Skip to main content

CLR Run-Time Breaking Changes

Short DescriptionThree overloads of Marshal.WriteInt16 that accept char as one of parameters write only one single byte.
Affected APIspublic static void WriteInt16(IntPtr, char); public static void WriteInt16(IntPtr, int, char); public static void WriteInt16(object, int, char);SeverityLowCompat Switch AvailableNo

DescriptionThe affected APIs always wrote only one byte. This is wrong in many situations, where two bytes are needed. An example of the issue: If you write 'a', the memory at that offset will be cc61.

User ScenarioAnybody who was using these APIs expecting two bytes to be written.

Work AroundMany consumers of the APIs already seemed to work around the known bug with code such as the following: Marshal.WriteInt16(logFont, LogFontNameOffset, (short)'@');


Short DescriptionTlbExp naming generation change
Affected APIsTlbExp.exe tool and type library export API TypeLibConverter.ConvertAssemblyToTypeLibrarySeverityLowCompat Switch AvailableNo

DescriptionCurrent behavior TlbExp generates a type library for mscorlib.dll as a part of our build process. This type library is then used from COM clients. TlbExp does some complicated renaming for classes/interfaces when it finds the naming conflicts between namespaces (since type libraries don't have namespaces). Unfortunately, it may rename classes/interfaces even when one of the conflicting classes/interfaces is private or COM invisible. Why is this important? It is discovered that as soon as we add a new namespace with generic classes to mscorlib, we'll break COM clients. These generic classes have the same names as non-generic ones that already exist. The generic classes are not visible to COM but cause the old ones to be renamed anyway.

User ScenarioVB6: VB6 project references a typelib by LIBID and version (and LCID). So as long as the v1.0 mscorlib.tlb is still on the machine, it won't be affected even on recompilation. If the machine only has a version 2.0 mscorlib.tlb, the reference would be automatically satisfied by that, and there's the potential of compile errors from changed names only if those types are being used. Note that references to typelibs should only be auto-upgraded if the major version is the same, so if version 2.0 is a 2.0 release and our typelibs have version 2.0, then referencing a version 2.0 typelib would have to be an explict action by the developer anyway (and if they wanted the old names they could install and reference the v1.0/v1.1 mscorlib.tlb). Of course, this same behavior applies to customer assemblies and typelibs as well. C++: Type libs are #imported by filename, so which mscorlib.tlb gets used (when there are multiple ones) can depend on the development environment, the source code, etc. So if recompilation picks up the version 2.0 mscorlib.tlb, compilation errors would occur only if those types are being used.

Work AroundUse the new TlbExp to force the old behavior if applicable.


Short Description Trailing slash behavior changed for DirectoryInfo.Parent, and Name.
Affected APIsSystem.IO.DirectoryInfo.Parent System.IO.DirectoryInfo.NameSeverityLowCompat Switch AvailableNo


In RTM, we had behavior on some of our IO classes which was simply inappropriate. One such case is DirectoryInfo.Parent. In RTM, if you called this API, and passed a path ending in a slash, then the path returned would simply give the same path, without the slash:

DirectoryInfo di = new DirectoryInfo(@"c:\temp\Dir\");
DirectoryInfo di2 = di.Parent();

In v1.0, the call to Parent would return "c:\temp\Dir" for the DirectoryInfo. However, this is not the way users expect this to behave: the backslash should simply have been ignored. We therefore changed this API to ignore a trailing backslash, so in v1.1, the above call will now return "c:\temp". As part of this change, DirectoryInfo.Name was changed. In v1.0, if a path which ended in a backslash, we would do no interpretation of the path at all, and simply return the fullname. In V1.1, we now determine that if you have specified 'c:\foo\bar', then the name simply refers to bar. This change in behavior is consistent with the change to Parent.

User ScenarioAnyone relying on trailing slash behavior of DirectoryInfo, on the Parent, or the Name.

Work AroundRemove trailing slashes from strings which are paths


Short DescriptionNativeOverlapped constructor changed from taking an int to IntPtr
Affected APIsSystem.Threading.NativeOverlapped classSeverityLowCompat Switch AvailableNo

DescriptionSome fields on the NativeOverlapped class needed to be IntPtr's instead of Int32's for 64 bit portability. We changed that. We believe this is the least cost fix. It's limited primarily to people implementing classes that support async IO. It's an advanced scenario, and people who hit this bug can probably figure it out easily.

User ScenarioUser implements a class like FileStream that supports async IO.

Work AroundChange your code to use an IntPtr.


Short DescriptionGreater precision introduced in Floating Point calculations
Affected APIsAny/All Floating Point manipulationSeverityLowCompat Switch AvailableNo


In the CLR model, we assert that arguments, locals, and return values (things which you can't tell their size) can be expanded at will. When you say float, it means anything greater than or equal to a float. So we can sometimes give you what you asked for 'or better'. When we do this, we can spill 'extra' data, almost like a 'you only asked for 15 precision points, but congratulations! We gave you 18!'. If someone expected the floating point precision to always remain the exactly the same, they could be affected. In order to faciliate performance improvements and better scenarios, the CLR may rewrite (as in this case) parts of the register. For example, things that used to truncate because of spilling, no longer do. We make these kinds of changes all the time. We believe this is an appropriate change, and it is even called out specifically in the CLI specification, as something which can, and will occur with different iterations:

User ScenarioSomeone relies upon a floating point value to have exactly a certain precision.

Work AroundNone


Short DescriptionSome DateTime Parsing which was acceptable but interpreted badly, is now no longer accepted
Affected APIsDateTime.Parse, or derivatives (such as CDate in VB)SeverityLowCompat Switch AvailableNo

DescriptionWe try to make DateTime.Parse accept a lot of input and make a reaosnable interpretation. However there are scenarios where it was interpreting data which was simply not reasonable. For example, the string "01-01-10/10/2001" would previously be successfully parsed, however this is clearly not a valid form for a DateTime. The scope of this change is limited, and will not affect any valid formats.

User Scenario Someone who relied on certain strings to be parsed, will now get an exception

Work AroundNone


Short DescriptionToString behavior on RegistryKey has been modified
Affected APIsRegistryKey.ToString()SeverityLowCompat Switch AvailableNo

Description RegistryKey.ToString previously returned the Handle to the Key (as a Hexadecimal string) at the end of the ToString() call. We no longer append this data to the end of the ToString().

User ScenarioAnyone relying on the result of RegistryKey.ToString(). Note that we specifically inform customers to NOT rely on ToString behavior, for any APIs

Work AroundNone


Short DescriptionCheck the type of type (class vs. struct) specified in a TypeRef in metadata to ensure that is correct. A TypeInitializationException will be thrown if the TypeRef is incorrect.
Affected APIsNoneSeverityLowCompat Switch AvailableNo


Affected code will look something like the following:TypeSpec

#4 (1b000004)
TypeSpec : Class System.Byte

It should however, look like:

TypeSpec #4 (1b000004)
TypeSpec : ValueClass System.Byte

The CLR accepted this incorrect metadata in v1.1. We have tightened down and do not accept it in v2.0.

User ScenarioThis would break you only if you were using a compiler of obfuscator that generated bad metadata.

Work AroundNone


Short DescriptionWaitHandle.Wait* will throw when the wait completes due to an abandoned mutex. The mutex will continue to be held.
Affected APIsWaitHandle.WaitOne, WaitHandle.WaitAny, WaitHandle.WaitAll (all overrides)SeverityLowCompat Switch AvailableNo

DescriptionWhen a wait function completes due to an abandoned mutex, it will throw instead of returning normally.

User ScenarioApplications that encounter abandoned mutexes will receive an exception.

Work Around Catch the exception and either validate or make inaccessible the protected data.


Short DescriptionThe 2.0 runtime looks for and consumes the /cor command line switch on all applications.
Affected APIsNoneSeverityLowCompat Switch AvailableNo

DescriptionThe runtime looks for the /cor command line switch when any application that is built with the latest version of the runtime is launched, uses the switch for ClickOnce deployment, and then does not pass the switch on to the application. Any application that happens to use a /cor command line switch will start getting FileNotFound exceptions when rebuilt and executed with the latest version of the runtime.

User ScenarioAny v1.x apps that used /cor for a command line switch will have to use a different switch when rebuilding or running on 2.0

Work AroundChange the /cor command line switch to something else.


Short DescriptionThrow AccessViolationException when an AV reaches managed code.
Affected APIsAny calls into unmanaged code through P/Invoke, any use of pointers in unsafe code.SeverityLowCompat Switch AvailableNo

DescriptionSplit NullReferenceException into NullReferenceException for "safe" errors, and AccessViolationException for "unsafe" ones.

User ScenarioApplications that take an AV will receive an AccessViolationException instead of a NullReferenceException.

Work AroundYou should not catch AccessViolationException, you should fix the underlying AV. As a temporary measure, a config-file switch will be provided to revert this behavior.


Short DescriptionUnhandled exceptions will always be fatal to a process.
Affected APIsN/ASeverityLowCompat Switch AvailableNo

DescriptionUnhandled exceptions will always be fatal to the process.

User ScenarioApplications that throw unhandled exceptions on threads other than the main thread (or ones that come into the runtime from the outside) will crash rather than continue running (potentially abnormally).

Work AroundPut a catch block at the top of your non-main thread, threadpool workitem, or finalizer. (Or else fix the bug that led to the exception.) As a temporary measure, a config-file switch will be provided to revert back to the old behavior.


Short DescriptionAdding unmanaged code permission link demand on some of the functions in the tracing classes.
Affected APIsSystem.Diagnostics.Trace, System.Diagnostics.TraceSource, System.Diagnostics.BooleanSwitch, System.Diagnostics.Debug, System.Diagnostics.SourceSwitch, System.Diagnostics.TraceSwitchSeverityLowCompat Switch AvailableNo

DescriptionThe change will allow the semi-trusted code to only write trace messages and it will prevent semi-trusted code from accessing listeners, correlation or modifying how the tracing is working.

User ScenarioSemi-trusted code trying to call methods that affects how tracing is working.

Work AroundIt is not recommended but customers can create a proxy to expose the tracing functionality they want to semi-trusted code because we use link demand.


Short DescriptionDisable inheriting from COM invisible classes and making derived classes COM visible.
Affected APIsThere are no specific APIs that are affected. This is a change in internal CLR behavior.SeverityLowCompat Switch AvailableNo

DescriptionEven when public non-sealed managed class (or the whole assembly) is marked as COM-invisible, somebody can inherit from such a class and make a subclass COM-visible. If such a subclass exposes a class interface of type AutoDual or type AutoDispatch (which is default), changes in the base class can break COM clients of a subclass. For example, if a new method is added on a base class (in a new version), a derived class with AutoDual interface will get different v-table layout and break native clients (since exposed v-table contain all public methods from base class as well as public methods of derived class). If a new overload is added on a base class, derived class with AutoDispatch interface will get its methods renamed (because of naming schema for overloaded methods) and again native clients will get broken. We don't believe that it is feasible to ask all developers of managed frameworks to maintain COM backward compatibility even in cases when their whole assemblies are marked as COM invisible and they clearly don't have any interesting COM clients. We also need to prevent people taking dependencies on behaviors we can't guarantee we'll preserve from version to version. This is only one step in that direction. It clearly unreasonable to put burden of preserving COM backward compatibility on every team that ships public non-sealed managed class.

User ScenarioAnybody who was dependent on inheriting from COM invisible classes and making derived classes COM visible.

Work Around
  1. Make base classes that depend on COM clients COM visible. (preferred)
  2. Change derived class to use class interface of type None and explicitly forward calls to base class methods. (preferred)
  3. We would also add a config switch that would revert back to the old behavior. Note that wouldn't be guarantee that things will work either but there is a chance that they will if there were no changes in the base classes. If some changes are indeed made to the base class, clients could be broken by it as described above.


Short DescriptionGetTypeFromCLSID returns the same type always, regardless of environment.
Affected APIsSystem.Type.GetTypeFromCLSIDSeverityLowCompat Switch AvailableNo


GetTypeFromCLSID() used to return a type object from tlbimp'ed assembly if there is a type with same GUID already loaded earlier. Such type used to yield 'false' when Type.IsComObject() is called. And, Type.GetMethodInfo() used to return whatever we could see in IL metadata assembly.


  • GetTypeFromCLSID() always returns a RuntimeType regardless of whether a type is already loaded with same GUID.
  • Type.IsComObject() returns true.
  • And, Type.GetMethodInfo() doesn't fetch any of the methods defined in tlb.

The reason we need to do this is to make things deterministic. In the V1 and V1.1 CLR, if the type has been loaded then we will wrap the created instance with the actual type, even if the Interop assembly for that type hasn't been registered. This can lead to unfortunate race conditions where, depending on what has currently been run in the AppDomain, the type will either be wrapped with an __ComObject or with the actual type. To remove these race conditions, we changed the behavior to only wrap the created COM object with the actual type if the Interop assembly is registered.

User ScenarioThis change would only break applications which are casting RCWs to specific types which, because of the way we import IAs, should not be very common. The other cases where it could possibly break is when reflecting on the type of the RCW; however we don't suspect this will be a common scenario.

Work AroundCustomer would need to register the Interop Assembly and then they will reliably get the COM object wrapped in the actual type


Short DescriptionThis breaking change request has two parts: 1) Always initialize main method as MTA unless user specified [STAThreadAttribute] on the main method. 2) Always initialize new threads as MTA unless user initialized it to STA before thread started.
Affected APIsThere are no specific APIs that are affected. This is a change in internal CLR behavior.SeverityLowCompat Switch AvailableNo


The problem with the V1 and V1.1 behavior is that depending on the app and the environment in which it is run (is it ngen'd, is it run off a network share, what are the security settings of the machine, etc), the CLR might need to start up COM to perform some of the startup code. When this happens, the CLR needs to start up COM before control enters the user's main and because we need to pick a default, we would CoInitialize it to MTA. What this would cause is that in some situations, your code below that does a CoInitialize in your Main method would fail. However under other circumstances it would pass. Changing the CLR to never use COM before the user's code is executed wasn't possible (it's not impossible, it is just way too much work and puts too many restrictions on the CLR to be viable) so we decided to make the behavior deterministic by always CoInitializing to MTA unless the [STAThreadAttribute] was specified on the main method.

Input from Christopher Brumme on this change:

"Whenever you set the apartment state of a running managed thread, you are in a race condition. That's because things like .cctors or security demands or assembly load notifications may have executed on that thread, perhaps causing marshaling of oleaut data—or any number of other things that could have caused a CoInitialize to happen. Even if the first line of your managed threadproc sets the apartment state, we have already executed an arbitrary amount of managed application code. And that arbitrary set of code changes whenever you upgrade the CLR, change the JIT inlining, fiddle with .cctor rules for NGEN vs. domain neutral, change security policy, etc.

If you are trying to set it to MTA, chances are you are okay. That's because we will default to MTA if we need to CoInitialize a thread. The only way someone would have set it to STA is if a .cctor or security infrastructure or hosting code had done this to you. And that would be pretty rude. Of course, if the CLR sets it to MTA and then the app sets it to MTA, this will be quietly accepted. So the only case that's likely to break is if the application selects STA but the CLR has already CoInitialized the running thread to MTA.

Anyway, the bottom line is that applications which set the apartment state of a running managed thread are perched on a knife edge. Long term, we need to move to a more stable and predictable world. The only question is the best way to do this. >We needed to change the behavior of 'main' on a managed EXE to be MTA if it is unspecified and we added a config setting for old C++ apps and for VB/C# apps that weren't built with Visual Studio.

The other change we added is for managed threads started with 't = new Thread(...); t.Start();'. If you didn't set the apartment state before you called Start, then it is now too late. We will have already placed you in the MTA. The same config setting to get the old behavior applies here too.

We realize that both of these changes will break apps. But those apps are going to break at some rate, as we disturb the race conditions anyway. We are providing the deterministic and reliable behavior now, know it will break some applications, rather than leaving in non-deterministic behavior that breaks applications with each new release."

User Scenarioo
  1. Anybody who was using Thread.CurrentThread.ApartmentState = ApartmentState.STA; as the first line in their Main method. For example, all WinForms applications generated with v1.1 C++ designer will break because of the such call in WinMain() that gets generated automatically. Note that they might be broken anyway (even without this change since they are dependent on any random changes in CLR initialization and on config changes).
  2. Anybody who was not initializing new thread as STA before they started it (but used to do it as first line of code that executes on that thread, for example).

Work AroundThis change 1) would only apply to apps that are compiled with v2.0(and after). Apps compiled with v1 and v1.1 would not see this but they might be broken on v2.0 anyway because they were dependent on the non-deterministic nature of apartment initialization. We would also provide config switch to revert back to old behavior for 2.0 apps (but this might save them per description above) or apps that use DLLs affected by 2).


Short DescriptionUTF7Encoding need to override the Equality
Affected APIsUTF7Encoding.Equals and UTF7Encoding.GetHashCodeSeverityLowCompat Switch AvailableNo

DescriptionUTF7Encoding has boolean flag which control if the optional characters are allowed in encoding. so equality should consider this flag in the comparison.

User Scenarioany app used to compare two UTF7Encoding objects may get different result than they get in v1.1.

Work AroundNo work around to consider the optional characters allowance flag except if the application keep track how UTF7Encoding object get created with this flag is true or false.


Short DescriptionChange the Equality operation in SortKey class
Affected APIsSortKey.EqualsSeverityLowCompat Switch AvailableNo

DescriptionSortKey.Equals is not consistent with SortKey.Compare which is used to compare two SortKey objects. We need to make equality operation behave like SortKey.Compare. Currently SortKey.Equals is not that helpful for the user to compare two SortKey objects.

User Scenario Any client used to compare two SortKey objects may get different result than they get in v1.1.

Work Arounduse SortKey.Compare


Short DescriptionStringInfo need to implement the Equality and GetHashCode.
Affected APIsStringInfo.Equals and StringInfo.GetHashCodeSeverityLowCompat Switch AvailableNo

DescriptionWe need to implement semantics comparison to compare the associated string in StringInfo. In v1.1, StringInfo contains only static methods so it is very unlikely anybody will be broken because of this change.

User Scenarioany app used to compare two StringInfo objects may get different result than they get in v1.1. v1.1 uses reference equality. Also storing StringInfo into hash table will be different than v1.1 too.

Work Aroundcompare the result of the String property instead. (stringInfo1.String == stringInfo2.String).


Short DescriptionUTF8Encoding needs to change Equals() implementation to consider throwOnInvalidBytes.
Affected APIsUTF8Encoding.Equals()SeverityLowCompat Switch AvailableNo

DescriptionUTF8Encoding can be constructed with encoderShouldEmitUTF8Identifier and throwOnInvalidBytes option. The Equals() implementation only compares the encoderShouldEmitUTF8Identifier, but not throwOnInvalidBytes. This can lead application to re-use an UTF8Encoding expecting not throwing exception, but will get exception when encoding/decoding invalid char/byte sequence.

User ScenarioIf application uses UTF8Encoding.Equals() to compare two instances of UTF8Encoding using different throwOnInvalidBytes parameter in the ctor, v1.1 will return true, while v2.0 it will return false.

Work AroundSince encoderShouldEmitUTF8Identifier is not exposed as public properties, app that replies on this behavior will have no workaround. However, it is unlikely that they will want to reply on this behavior.


Short Description Encoding.GetBytes() may not emit unpaired high or low surrogate characters for certain encodings (e.g. UTF-8 Encoding and UnicodeEncoding).
Affected APIsEncoding.GetBytes()SeverityLowCompat Switch AvailableNo


For Unicode standard compliance, Encoding.GetBytes() will not emit bytes if there is an unpaired or out of order surrogate. This is most obvious if the caller call GetBytes() with one high or low surrogate. In this case, UTF8Encoding and UnicodeEncoding will not emit nothing.

The Unicode 4.0 requires that compliant applications not emit unpaired surrogates. In v1.1, GetBytes() will emit bytes for lone surrogates if the encoding supports it (such as UTF-8 and UnicodeEndcoding). However, this leads CLR not to be Standard compliance with Unicode 4.0.

The change can break application's assumption about that GetBytes() will emit leading high surrogates or mismatched surrogates. BinaryWriter.Write(char ch) is one example of being broken.

User ScenarioIf the application assumes that GetBytes() will emit high or surrogate if it is called with one surrogate (1/2 of the pair) at a time, or will emit a surrogate at the end of the character buffer, they may lose the ability to correctly generate surrogate pairs.

Work AroundTo fix this issue, applications have to either use the encoder or throw exception on unpaired surrogates.


Short DescriptionBreaking Change: DisplayName of version 2.0 assemblies to contain processor architecture
Affected APIsAssembly.FullName, Type.AssemblyQualifiedName , the ResolveEventArgs.Name passed to AppDomain.AssemblyResolve. SeverityLowCompat Switch AvailableNo

DescriptionThe proposed change is to have the DisplayName of version 2.0 assemblies contain the processor architecture. This change is required because processor architecture is an inherent property of a version 2.0 assembly identity (just like other properties: name, version, culture). Omitting the processing architecture in the display name will lead to long-standing customer confusion because the identities reported by managed code do not accurately reflect their true identity. Note that only version 2.0 apps (and not version 1.1 apps) will be affected by this change, as only version 2.0 assemblies will have processor architecture in their identity.

User ScenarioAny code that uses string comparison for comparing identities between refs and defs will be broken by this change. String comparison is not the correct way to compare identities. Note that only version 2.0 assemblies are affected as only version 2.0 assemblies have processor architecture in the display name. version 1.1 assemblies are not affected.

Work AroundWe will include processor architecture in the display name for version 2.0 assemblies only (not version 1.1). We will provide a managed API to do ref-def matching (This will be a static method on AssemblyName with a Boolean result specifying match success/failure). A cost of this change is everyone using the resolve hook now must call this new API; however this cost is justified as we are steering people in the right direction (of not using string comparison for comparing identities).


Short DescriptionCompareInfo.GetSortKey should throw an exception when CompareOptions is out of range
Affected APIsSystem.Globalization.CompareInfo.GetSortKeySeverityLowCompat Switch AvailableNo

DescriptionCompareInfo.GetSortKey should throw an exception when CompareOptions is out of range, previously it does nothing and essentially ignores the additional input.

User ScenarioIf an application was accidentally passing inputs in that are out of range they will now be notified of it via an exception rather then it being silently ignored

Work AroundChange application to use inputs in the proper range.


Short DescriptionChange SortKey.GetHashCode() to match SortKey.Equals() behavior
Affected APIsSystem.Globalization.CompareInfo.SortKey.GetHashCodeSeverityLowCompat Switch AvailableNo


Although breaking, we would need to go head and fix this to provide more consistent behavior, justification:

  1. With the current behavior, Equals and GetHashCode are not implemented the same way, if two objects are compared equal they should have the same hash code. The current implementation of GetHashCode is based on the name of the sortkey and so returns random results.
  2. The current behavior (based on instance? yuck!) has no true utility, where as the fixed behavior (creating a hashcode based on the string) does, since it could be used as an optimization for comparisons with a hashcode index and a sortkey index.
  3. There is no other good way to give the behavior since we cannot add a CultureInfo or CompareInfo to a sortkey (which already has one implied by its parent object from which it was created).

User ScenarioSee Description

Work Aroundnone


Short DescriptionRSACryptoServiceProvider and DSACryptoServiceProvider delay creation of a random key, causing exceptions to be thrown later than they were before.
Affected APIsRSACryptoServiceProvider and DSACryptoServiceProvider constructorsSeverityLowCompat Switch AvailableNo

DescriptionWe now delay the creation of the random key in these classes until it is actually needed. This was done to increase performance and avoid unnecessary overhead when a random key was not needed (i.e., the class is created and a key is immediately imported). A side-effect of this, however, is that exceptions which were thrown in the constructor (such as for an invalid key size) are now thrown later or not at all. If someone were using random keys, relying on this behavior, and doing all error checking at construction time, this would break them.

User ScenarioUsers of these crypto classes

Work AroundChange error-checking code (would have to recompile)


Short DescriptionKyrgyzstan tag is now KG, matching official ISO tag
Affected APIsCulture data, which shows up through APIs such as CultureInfo and ResourceManager.GetString.SeverityLowCompat Switch AvailableNo

Descriptionv1.1 and v1.0 had the wrong tag for Kyrgyzstan, KZ. Fixed this in v2.0 so the tag for Kyrgyzstan is now KG. KG is the official ISO tag for kyrgyzstan. There is no country whose official ISO tag is KZ.

User ScenarioUsing the official ISO tag for Kyrgyzstan will now work. Using the incorrect tag will no longer work. If someone had created resources, tagged them as ky-kz, then did a ResourceManager.GetString("resid", "ky-KZ"), we will fail when trying to create the CultureInfo for ky-KZ, and will also therefore fail to find the resources tagged ky-KZ.

Work AroundUpdate code and resources to use the new tag, ky-KG.


Short DescriptionGlobalization: DateTimeFormatInfo; 'U' format string for Datetime are missing
Affected APIsDateTimeFormatInfoSeverityLowCompat Switch AvailableNo

Description// Assume the user's current calendar is JapaneseCalendar.

CultureInfo ci = new CultureInfo("ja-JP"); // Create Japanese culture
ci.DateTimeFormat.Calendar = new GregorianCalendar(); // Switch to Gregorian calendar
// The Japanese format is printed out, while Gregorian format is expected.

User ScenarioJapanese culture settings, switching to Gregorian calendar

Work Aroundnone


Short DescriptionDATA: Breaking Change - 4 cultures (ar-MA, nn-NO, kn-IN & div-MV) have misspelled day or month names.
Affected APIsnoneSeverityLowCompat Switch AvailableNo

DescriptionMisspelled day/month names cause parsing errors

User ScenarioAny of the following culture settings: ar-MA, nn-NO, kn-IN & div-MV

Work Aroundnone


Short DescriptionDATA: Breaking (rarely) month/day name changes
Affected APIsnoneSeverityLowCompat Switch AvailableNo

DescriptionMisspelled day/month names cause parsing errors

User ScenarioAny of the following cultures, zh-CN (chinese, china) uk-UA (Ukranian, Ukraine) bg-BG (Bulgarian (Bulgaria)) nn-NO (Norwegian, Nynorsk (Norway)), and Hebrew

Work Aroundnone


Short DescriptionBreaking Change: _Module in System::Runtime::InteropServices conflicts with global CComModule(ATL) object _Module
Affected APIsInteropServices._ModuleSeverityLowCompat Switch AvailableNo

DescriptionThis is a build time issue only and the compiler will emit a descriptive warning about the issue.

User ScenarioCompiling a c++ app with /clr and adding a "using System.Runtime.InteropServices" will cause a conflict if the code uses either _Module

Work AroundThe workaround is to not add the "using System.Runtime.InteropServices" and instead qualify InteropServices._Module rather than _Module


Short DescriptionDCR to make Boxed Value Types read-only
Affected APIsnoneSeverityLowCompat Switch AvailableNo

DescriptionThe breaking change is that in MultiDomain sharing you can now cause exceptions if you apply policy out of order. The apphack will ensure that in the case of applying policy to an existing domain that already has assemblies loaded, MultiDomain sharing will be treated like MultiDomainHost (which, as a permission hint, will have a perf penalty for these scenarios).

User ScenarioSee description

Work AroundNone


Short DescriptionString comparison and sorting for sr-SP-Latin(Serbian) culture is incorrect
Affected APIsString.CompareSeverityLowCompat Switch AvailableNo

Descriptiona known issue with the Windows XP, Windows Server 2003, and the .NET Framework's sorting tables.

User ScenarioString comparison and sorting for sr-SP-Latin(Serbian) culture

Work Aroundnone


Short DescriptionType.GetType(String typeName) throws if the type name contains the '&;' character
Affected APIsType.GetTypeSeverityLowCompat Switch AvailableNo

Descriptionthe characters &;+*[] are now reserved characters for type names, using them will throw an exception

User Scenariousing the characters &;+*[] in the type name parser

Work Aroundnone


Short DescriptionCanonicalization issue affecting Demand semantics in FileIOPermission
Affected APIsemptySeverityLowCompat Switch AvailableNo

DescriptionDemand for a path name that is a subset of a different path name (e.g. "d:\foo" and "d:\foobar") is affected by security settings of the other, superset path

User Scenariouser sets security on "d:\foo" but not "d:\foobar"

Work Aroundempty


Short DescriptionBCL: StringBuilder constructor doesn't work as expected if capacity=0 and Maxcapacity 16/td>
Affected APIsSystem.Text.StringBuilder .ctorSeverityLowCompat Switch AvailableNo

DescriptionThis change is essential for ECMA compatibility. Achieving, and sustaining ECMA compatiblity is a fundamental requirement of the BCL. The scenario of appending a string to a stringbuilder which exceeds the maximum capacity specified for the stringbuilder is also, simply not an interesting scenario, especially when the affected scenario is constrained to situations where the capacity must be less than the default capacity.

User ScenarioA user mistakenly appends more characters to a stringbuilder than they specific as the maximum capacity, AND the appended string is less than 16 characters.

Work AroundChange the maximum capacity to valid value


Short DescriptionBehavior change: % format produces different results under version 2.0 and EVE
Affected APIsIFormattable.ToString()SeverityLowCompat Switch AvailableNo

Descriptionin V1.1, if you tried to format using "%." via IFormattable.ToString, the number would simply be ignored in V1.1, and "%" would be printed. However, the standard intent of . is to define a decimal place, and it is therefore more reasonable to expect it is a placeholder for the number. Therefore, this behavior has been changed to print (for an integer such as 5), "%500", which is far more robust, and better reflect the user expectation

User Scenarionone

Work AroundChange the original format string and remove the . if you don't want the value to be printed.


Short DescriptionStreamReader used to ignore invalid UTF-* characters. Now it replaces them with "?"
Affected APIsSystem.IO.StreamReaderSeverityLowCompat Switch AvailableNo

DescriptionThe default reader encoding is utf8. It can be possible to read a file which will end up having invalid characters in it for that format. Realistically, the best thing to have done would have been to throw, informing the user that those invalid characters are in the file. That would be too much of a breaking change however, so instead of simpy ilently swallowing those invalid characters (and thus, never letting the user know there's something bad going on) we now place a '?' character in their place, so the user can be aware of the issue

User ScenarioA user who got lucky and expected a particular format when parsing information read from a file, and was relying on the inappropriate data from the file being ripped from the file. You would have to expect very specific lengths in the format to do this, something we would already strongly urge against, or expect that a specific section could be parsed into some specific value (such as an int).

Work AroundFix the invalid data in the file being read: there should be no invalid characters in files being read


Short DescriptionOrder of invocation of cctors has changed from V1.1 to V2.0 for beforefieldinit types in NGENed code.
Affected APIsNoneSeverityVery LowCompat Switch AvailableNo


Cctor ordering for beforefieldinit types is not specified by ECMA. ECMA only specifies that the runtime will execute cctors on beforefieldinit types some time prior to any access to a type's static field. It does not offer any guarantees regarding exactly when and in which order these cctors should be executed.

Note: C# automatically marks types as beforefieldinit when static fields are initialized using "=" syntax like in "static int i = 5;". VB.NET programs are not affected since they use precise cctor semantics always.

User ScenarioAny V1.0 or V1.1 compiled code that had dependency on cctor order execution and the cctors were on beforefieldinit types and the code was NGENed. (See sample code for an example of cctor order execution dependency)

Work Arounda) Use V1.1 NGEN b) Use V1.1 or V2.0 JIT c) Change code to remove dependency on cctor ordering


Short DescriptionEnvironment.UserDomainName now returns the correct value for name conflict scenarios
Affected APIsEnvironment.UserDomainNameSeverityVery LowCompat Switch AvailableNo

DescriptionIn V1.1, if a domain user (e.g. 'SomeDomain\UserA') was logged into a machine (e.g. 'Test001')and the machine also had a local user account with the same name as the domain user (e.g. 'UserA'), then accessing the UserDomainName property would actually return the machine name, NOT the domain name. The same thing would happen if the domain user was logged in, and his/her name was the same as the name of the machine. This has been fixed, so the user's domain name is ALWAYS returned.

User ScenarioA user has a local and domain name of the same name, and uses the user domain name property

Work AroundNone


Short DescriptionCache load failures in order to ensure that different app domains do not have different dependency loading success/failure characteristics in domain neutral sharing scenarios.
Affected APIsDeprecating Appdomain.AppendPrivatePath and Appdomain.ClearPrivatePathSeverityVery LowCompat Switch AvailableNo


In v1.0 and v1.1, some pathways through the loader recorded a failure to load an assembly and would fail subsequent attempts to load that same assembly into the same AppDomain. However, most pathways through the loader would not cache this binding failure. This lack of caching enables certain scenarios, which some customers are doubtless taking advantage of.

In v2.0, the default behavior is to break these scenarios. Naturally the .NET Framework supports an opt-in mechanism to force the loader back to the v1.0 & v2.0 behavior via a config file. These scenarios are broken by the following changes made in v2.0:

  • All binding failures per assembly per AppDomain are cached. Subsequent attempts would unconditionally continue to fail. Transient errors like OutOfMemoryException are not considered binding failures and are subject to retry.
  • AppDomain.AppendPrivatePath and ClearPrivatePath are deprecated. These APIs allow the application to dynamically change where the .NET Framework looks for assemblies to satisfy binding requests. These APIs are subject to race conditions and cannot be reliably used.

User ScenarioThis change would break code that is dependent on changing the private path of the app-domain after the first assembly has been loaded. This is actually less common than one would think. ASP.NET thought that they relied on this and later learned that they did not.

Work AroundNone


Short DescriptionChange Activator.CreateInstance and Type.InvokeMember to deterministically choose binding preferences, rathter than depending on declaraiton order.
Affected APIsActivator.CreateInstance, Type.InvokeMember,SeverityHighCompat Switch AvailableNo


The v1.x binder was arbitrarily assigning binding preferences between two methods. For example, in v1.x if you swap the declared order of M0 (or M2) the binder will bind to the other method.

using System;
using System.Reflection;

class C
public void M0(object arg) { Console.WriteLine(2); }
public void M0(ref int arg) { Console.WriteLine(1); }

public void M1(ref object arg) { Console.WriteLine(2); }
public void M1(ref int arg) { Console.WriteLine(1); }
class Repro
static void Main()
// Result depends on the order the M0 overloads were declared
BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod,
null, new C(), new object[] { 100 }, null);

// Result depends on the order the M1 overloads were declared
typeof(C).InvokeMember("M1", BindingFlags.Public | BindingFlags.Instance |
null, new C(), new object[] { 100 }, null);

Now, in v2.0, we throw an AmbiguousMatchException and inform the developer about the ambiguous match rather than leave in the arbitrary, and prone-to-change, behavior of previous releases.

User Scenario.

Work Around.


Short DescriptionEnterprise Library June release configuration tool fails when run against v2.0
Affected APIsmscorlibSeverityMediumCompat Switch AvailableNo

DescriptionThe Enterprise Library configuration tool throws an invalid cast exception when run against 2.0 when trying to use the Registry Storage Provider.

User ScenarioUsers implementing the Enterprise Library Configuration block will run into this exception

Work AroundTo fix this, they added the following to their code, but customers who compiled on v1.1 will still be broken by this:#if VS2005B2
SafeHandle handle = infoField.GetValue(configKey) as SafeHandle;
IntPtr keyHandle = handle.DangerousGetHandle();


Short DescriptionNullReferenceException when calling GetEnumerator on a SynchronizedHashtable cast to ICollection
Affected APIsSystem.Collections.Hashtable.Synchronized, System.Collections.Hashtable.GetEnumeratorSeverityMediumCompat Switch AvailableNo

DescriptionSynchronizedHashtable is a wrapper around Hashtable. It inherits from Hashtable but routes all calls to the hastable it is wrapping after making the appropriate locks to synchronize access. If you cast the synchronized hastable to ICollection or IEnumerable and try to call ForEach on it, in v2.0 it will throw a NullReferenceException. In v1.1 it would not throw but it would return an enumerator with no elements, even if the hashtable in question was not empty. Prior to v2.0 the synch hashtable would allocate a array that it would never use. In v2.0, this redundant allocation was removed to save working-set size. However, there is a bug that if you cast the SynchronizedHastable to ICollection or IEnumerable, it does not forward to the wrapped object and gives you an empty enumerator to the outer one. Because the array is no longer allocated, in V2.0 this causes a NullReferenceException instead.

User ScenarioHashtable h = new Hashtable();
(h as ICollection).GetEnumerator();
Hashtable h2 = Hashtable.Synchronized(h);
(h2 as ICollection).GetEnumerator();
In version 2.0, the last call throws NullReferenceException. In V1.1 it returns an empty enumerator.

Work AroundTo correct both problems, do not call the enumerator on a synchronized hastable that been casted or converted to IEnumerable or ICollection. Instead, enumerable on the strongly typed Hashtable object, or fetch the enumerator while it is still typed as Hashtable.


Short DescriptionReleasing an unowned monitor throws an exception.
Affected APIsSystem.MonitorSeverityLowCompat Switch AvailableNo

DescriptionIn some circumstances in v1.1, we don't throw an exception if you tried to release an unowned monitor. This now throws an exception in v2.0.

User ScenarioImbalanced Monitor.Enter/Exits

Work AroundOnly release monitors that are owned.


Short DescriptionWaitHandle.WaitAll throws an exception that seemingly cannot be caught if there's a null element in the array of wait handles; this is different from v1.1 behavior.
Affected APIsWaitHandle.WaitAllSeverityMediumCompat Switch AvailableNo


If you pass in an array with one or more null entries into the API WaitHandle.WaitAll, in V1.1 this would throw an ArgumentNullException. In V2.0 this will throw a FatalExcecutionException, which cannot be caught and will tear down the process.

This happens because of a coding error. There is code to detect this situation and throw an exception, but some code earlier in the routine puts the array in a special wrapper that dereferences all the entries in it, which causes an AV in the VM, which tears down the process because that is the policy for failures of this type.

User ScenarioIf you pass in an array with one or more null entries into the API WaitHandle.WaitAll, in V1.1 this would throw an ArgumentNullException. In V2.0 this will throw a FatalExcecutionException, which cannot be caught and will tear down the process.

Work AroundDon't leave any null values in the array passed to these APIs.


Short DescriptionApplications dependent on the implementation of private fields could be broken if the type of the field changes for example from an IntPtr to a SafeHandle.
Affected APIsSystem.IntPtr()SeverityMediumCompat Switch AvailableNo


Applications dependent on the implementation of private fields could be broken when the private field's type changes.

For instance, an app could use reflection to obtain a private field and try casting that field to an illegal type.

This has broken applications such as the Enterprise Library configuration tool, where the field type used to be safely cast to an IntPtr value, but since it has changed to a SafeHandle in v2.0 can no longer be cast this way.

User ScenarioA user has code that casts a field that they obtained through reflection from one type to another. The field's type changes to one which cannot be cast the same way as the old type.

Work AroundDo not rely on private implementation details whenever possible. We make no guarantees about maintaining compatibility with private members on types.


Short DescriptionString.GetHashCode and Object.GetHashCode algorithms have changed.
Affected APIsString.GetHashCode, Object.GetHashCode, HashtableSeverityMediumCompat Switch AvailableNo

DescriptionVarious performance tweaks have been made to the hashing algorithms of String and Object. One consequence of this is that code that depends on the exact value of the hash code, or the exact ordering of elements in a Hashtable, can be broken from version to version. Users should avoid depending on hash code values, as it makes code very fragile.

User ScenarioA user populates a Hashtable, dumps the contents somewhere, and then has some code that depends on the exact ordering of the output. This is particular easy to do in things like tests where the output is diffed against a fixed file, so it can depend on exact Hashtable ordering.

Work AroundThe work around is to sort Hastable output after population if the order must be preserved over time, or otherwise don't depend on exact Hashcode values.


Short DescriptionStack Trace was misreporting nested classes in v1.1, this has been fixed in v2.0.
Affected APIsStackTrace() class, Environment.StackTrace, Exception.ToString()SeverityMediumCompat Switch AvailableNo


The StackTrace dump in Environment and Exception had an error in V1.1 where it did not report nested type names correctly:

namespace A {
public class B {
public class C {
public string f() {
return new StackTrace().ToString

In V1.1 this reported "A.C.f()". In V2.0 the error was corrected to report "A.B.C.f()". Test code that baselines stack traces or code that cracked the text output of stack traces could have taken a dependency on the original bug.

User ScenarioTest code that baselines stack traces or code that cracked the text output of stack traces could have taken a dependency on the original bug.

Work AroundNone.


Short DescriptionPrivate typedefs may not have the same name in assemblies built for v2.0 as they did in v1.1.
Affected APIsAssembly.LoadSeverityMediumCompat Switch AvailableNo

DescriptionIn v1.1, assemblies were allowed to have Private typedefs that had the same name. In v2.0 we disallowed this behavior. We still allow assemblies compiled for v1.1 or v1.0 to have private typedefs with the same name. However, assemblies compiled for v2.0 with this behavior will cause an exception when loaded.

User ScenarioAn app was obfuscated so that all Private typedefs had the name "$". If compiled for v2.0, this app will fail when the obfuscated assemblies are loaded.

Work AroundAn assembly compiled for v2.0 that needs obfuscation must be obfuscated so that Private typedef names differ. Already released apps with assemblies that were compiled for v1.1 will continue to work and need no workaround.


Short DescriptionLoadfrom after Load bypasses Load context cache, and now loads from Cache.
Affected APIsAssembly.LoadFrom()SeverityMediumCompat Switch AvailableNo

DescriptionIn V1.1, assembly requests via System.Reflection.Assembly.LoadFrom() do not consult the Load context cache, just the LoadFrom cache. In the case that the assembly is already loaded in the Load context cache, the assembly is re-loaded from disk. This behavior actually does not make a lot of sense. This change results in a small performance increase for applications.

User ScenarioA program loads an assembly via Assembly.Load() and then Assembly.LoadFrom(). In the past, the assembly would need to be re-loaded. In v2.0, the assembly first loaded via Assembly.Load() is used by calls to Assembly.LoadFrom()

Work AroundThere is no workaround for this change. It is expected to be a positive change for developers and existing programs.


Short DescriptionUnhandled exceptions will always be fatal to a process
Affected APIsN/ASeverityMediumCompat Switch AvailableNo

DescriptionUnhandled exceptions will always be fatal to the process. They weren't necessarily always fatal in V1.0/V1.1.

User ScenarioApplications that throw unhandled exceptions on threads other than the main thread (or ones that come into the runtime from the outside) will crash rather than continue running (when they are potentially in an invalid state)

Work Around

Put a catch block at the top of your non-main thread, threadpool workitem, or finalizer. (Or else fix the bug that led to the exception.)

Alternatively, in the section of the application's config file, add the following:<legacyUnhandledExceptionPolicy enabled="1"/>



Short DescriptionOrder of invocation of class .ctors is changed from V1.1 to V2.0 for beforefieldinit types in NGEN
Affected APIsNo APIs, this is internal CLR changeSeverityMediumCompat Switch AvailableNo


Class .ctor ordering for beforefieldinit types is not specified by ECMA. ECMA only specifies that runtimes will execute clas .ctors on beforefieldinit types some time prior to any access to a type's static field. It does not offer any guarantees regarding when exactly this might happen and in which order exactly. This of course never stopped anybody to (unknowingly) take a dependency on unspecified behavior if the behavior is consistent on the same runtime version.

Note: C# automatically marks types this way when static fields are initialized using "=" syntax like in "static int i = 5;". VB.NET programs are not affected since they use precise class .ctor semantics always.

An FxCop rule has been added to help detect the invalid pattern.

User ScenarioAny V1.0 or V1.1 compiled code that had dependency on a class .ctor order execution, these class .ctors are on beforefieldinit types, and uses NGEN

Work AroundUse V1.1 NGEN; or use the V1.1 or V2.0 JIT (the JIT is unaffected by this change)


Short DescriptionA LockCookie for one ReaderWriterLock can be used to restore a different ReaderWriterLock
Affected APIsSystem.Threading.LockCookieSeverityMediumCompat Switch AvailableNo


A thread can save the state of ReaderWriterLock into a LockCookie, and restore the state in the future using the saved LockCookie. A LockCookie can be serialized to a buffer. The buffer can be modified and then deserialized to a LockCookie. Now a thread can use the new LockCookie to restore the state of ReaderWriterLock. A LockCookie for one ReaderWriterLock can be used to restore a different ReaderWriterLock.

In addition, a LockCookie contains thread information about the thread to which a lock belongs. Serializing to a buffer, changing the thread id, and deserializing could open possible desynchronization attacks against arbitrarily chosen threads, provided that their identifiers were known or could be guessed

User ScenarioThere are no valid usages where a user would use a LockCookie from one lock to interact with another.

Work AroundNone


Short DescriptionThe values inside 2 enums (PerformanceCounterPermissionAccess and AttributeTargets) have changed.
Affected APIsperformancecounterSeverityMediumCompat Switch AvailableNo


In 1.1 the three values in the enum System.Diagnostics.PerformanceCounterPermissionAccess were Browse:6, Instrument:2, Admin:14. These have been changed to browse:3, Instrument:1, Admin:7

Additionally, the value of System.AttributeTargets.All has changed from 16383, to 32767

User ScenarioInitialize PerformanceCounterPermission with PerformanceCounterPermissionAccess (only used in one of 4 constructors for PerformanceCounterPermissionAccess)

Work Aroundnone


Short DescriptionV1.1 stores raw handles in the WaitHandle.Handle property, while V2.0 wraps all raw handles in a SafeWaitHandle.
Affected APIsSystem.ThreadingSeverityMediumCompat Switch AvailableNo


IntPtr oldHandle = waitHandle.Handle; Win32.CloseHandle( oldHandle ); waitHandle.Handle = Win32.CreateEvent( );

From V1.1 to V2.0 the WaitHandle class has changed. It now points to a SafeWaitHandle, not to the raw handle.

Getting the handle and closing it is bad. The SafeWaitHandle still tracks the old handle and it will try to close it at finalize time. This means that the handle can be attempted to be closed twice.

User ScenarioThreadpool code tries to do a DangerousRelease on a SafeWaitHandle that has already been disposed.

Work AroundEither avoid closing the handle manually in the finalizer for the class (the runtime will take care of that anyway), or unregister the WaitHandle from the ThreadPool before closing the handle. The first option is preferable.


Short DescriptionCode that calls virtual functions non-virtually in partial trust fails with a System.Security.VerificationException when run against .NET Framework 2.0.
Affected APIsNone


The .NET Framework 1.1 C# compiler in certain cases generates code that calls virtual methods non-virtually. One of these cases is where there is a switch statement based on strings where the number of cases is greater than 8. In .NET Framework 2.0 a change was made to the verification rules for security purposes to disallow calling virtual methods non-virtually (in most cases). Therefore when code that was compiled using the .NET Framework 1.1 compiler is executed against .NET Framework 2.0, and the compiled MSIL binary contains a normal CALL (instead of a CALLVIRT) to a virtual method, and the assembly is running in partial trust, the MSIL needs to be verified, and it fails verification because of the new rule. The failure shows up as a System.Security.VerificationException.

Work Around

The next broad release of the .NET Framework (2.0 SP1 or Orcas) has a workaround for this issue. The workaround is to modify the machine.config file for the machine where the application is running by replacing the default "<runtime />" with:

    <legacyVirtualMethodCallVerification enabled="1" />