Threading Design Guidelines
.NET Framework 1.1
5 out of 14 rated this helpful - Rate this topic
- Avoid providing static methods that alter static state. In common server scenarios, static state is shared across requests, which means multiple threads can execute that code at the same time. This opens up the possibility for threading bugs. Consider using a design pattern that encapsulates data into instances that are not shared across requests.
- Static state must be thread safe.
- Instance state does not need to be thread safe. By default, class libraries should not be thread safe. Adding locks to create thread-safe code decreases performance, increases lock contention, and creates the possibility for deadlock bugs to occur. In common application models, only one thread at a time executes user code, which minimizes the need for thread safety. For this reason, the .NET Framework class libraries are not thread safe by default. In cases where you want to provide a thread-safe version, provide a static Synchronized method that returns a thread-safe instance of a type. For an example, see the System.Collections.ArrayList.Synchronized Method and the System.Collections.ArrayList.IsSynchronized Method.
- Design your library with consideration for the stress of running in a server scenario. Avoid taking locks whenever possible.
- Be aware of method calls in locked sections. Deadlocks can result when a static method in class A calls static methods in class B and vice versa. If A and B both synchronize their static methods, this will cause a deadlock. You might discover this deadlock only under heavy threading stress.
- Performance issues can result when a static method in class A calls a static method in class A. If these methods are not factored correctly, performance will suffer because there will be a large amount of redundant synchronization. Excessive use of fine-grained synchronization might negatively impact performance. In addition, it might have a significant negative impact on scalability.
- Be aware of issues with the lock statement (SyncLock in Visual Basic). It is tempting to use the lock statement to solve all threading problems. However, the System.Threading.Interlocked Class is superior for updates that must be atomic. It executes a single lock prefix if there is no contention. In a code review, you should watch out for instances like the one shown in the following example.
VB
SyncLock Me
myField += 1
End SyncLock
[C#]
lock(this)
{
myField++;
}
If you
replace the previous example with the following one, you will improve
performance.
VB
System.Threading.Interlocked.Increment(myField)
[C#]
System.Threading.Interlocked.Increment(myField);
Another
example is to update an object type variable only if it is null (Nothing in
Visual Basic). You can use the following code to update the variable and make
the code thread safe.
VB
If x Is Nothing Then
SyncLock Me
If x Is Nothing Then
x = y
End If
End SyncLock
End If
[C#]
if (x == null)
{
lock (this)
{
if (x == null)
{
x = y;
}
}
}
You can
improve the performance of the previous sample by replacing it with the
following code.
VB
System.Threading.Interlocked.CompareExchange(x, y, Nothing)
[C#]
System.Threading.Interlocked.CompareExchange(ref x, y, null);
- Avoid the need for synchronization if possible. For high traffic pathways, it is best to avoid synchronization. Sometimes the algorithm can be adjusted to tolerate race conditions rather than eliminate them.
Array Usage Guidelines
.NET Framework 1.1
Arrays vs. Collections
Class library designers might need to make difficult decisions about when to use an array and when to return a collection. Although these types have similar usage models, they have different performance characteristics. In general, you should use a collection when Add, Remove, or other methods for manipulating the collection are supported.For more information on using collections, see Grouping Data in Collections.
Array Usage
Do not return an internal instance of an array. This allows calling code to change the array. The following example demonstrates how the arraybadChars
can be changed by any code that
accesses the Path
property
even though the property does not implement the set accessor.
VB
Imports System
Imports System.Collections
Imports Microsoft.VisualBasic
Public Class ExampleClass
NotInheritable Public Class Path
Private Sub New()
End Sub
Private Property Path
Get
End Get
Set
End Set
End Property
Private Shared badChars() As Char = {Chr(34),"<"c,">"c}
Public Shared Function GetInvalidPathChars() As Char()
Return badChars
End Function
End Class
Public Shared Sub Main()
' The following code displays the elements of the
' array as expected.
Dim c As Char
For Each c In Path.GetInvalidPathChars()
Console.Write(c)
Next c
Console.WriteLine()
' The following code sets all the values to A.
Path.GetInvalidPathChars()(0) = "A"c
Path.GetInvalidPathChars()(1) = "A"c
Path.GetInvalidPathChars()(2) = "A"c
' The following code displays the elements of the array to the
' console. Note that the values have changed.
For Each c In Path.GetInvalidPathChars()
Console.Write(c)
Next c
End Sub
End Class
[C#]
using System;
using System.Collections;
public class ExampleClass
{
public sealed class Path
{
private Path(){}
private static char[] badChars = {'\"', '<', '>'};
public static char[] GetInvalidPathChars()
{
return badChars;
}
}
public static void Main()
{
// The following code displays the elements of the
// array as expected.
foreach(char c in Path.GetInvalidPathChars())
{
Console.Write(c);
}
Console.WriteLine();
// The following code sets all the values to A.
Path.GetInvalidPathChars()[0] = 'A';
Path.GetInvalidPathChars()[1] = 'A';
Path.GetInvalidPathChars()[2] = 'A';
// The following code displays the elements of the array to the
// console. Note that the values have changed.
foreach(char c in Path.GetInvalidPathChars())
{
Console.Write(c);
}
}
}
You can correct the problem in the preceding example by making the badChars
collection readonly (ReadOnly
in Visual Basic). Alternately, you can clone the badChars
collection before returning. The following
example demonstrates how to modify the GetInvalidPathChars
method to return a clone of the badChars
collection.
VB
Public Shared Function GetInvalidPathChars() As Char()
Return CType(badChars.Clone(), Char())
End Function
[C#]
public static char[] GetInvalidPathChars()
{
return (char[])badChars.Clone();
}
Do not use readonly (ReadOnly in Visual Basic) fields of
arrays. If you do, the array is readonly and cannot be changed, but the
elements in the array can be changed. The following example demonstrates how
the elements of the readonly array InvalidPathChars
can be changed.
Operator Overloading Usage Guidelines
.NET Framework 1.1
5 out of 7 rated this helpful - Rate this topic
- Define operators on value types that are logical built-in language types, such as the System.Decimal Structure.
- Provide operator-overloading methods only in the class in which the methods are defined. The C# compiler enforces this guideline.
- Use the names and signature conventions described in the Common Language Specification (CLS). The C# compiler does this for you automatically.
- Use operator overloading in cases where it is immediately obvious what the result of the operation will be. For example, it makes sense to be able to subtract one Time value from another Time value and get a TimeSpan. However, it is not appropriate to use the or operator to create the union of two database queries, or to use shift to write to a stream.
- Overload operators in a symmetric manner. For example, if you overload the equality operator (==), you should also overload the not equal operator(!=).
- Provide alternate signatures. Most languages do not support operator overloading. For this reason, it is a CLS requirement for all types that overload operators to include a secondary method with an appropriate domain-specific name that provides the equivalent functionality. It is a Common Language Specification (CLS) requirement to provide this secondary method. The following example is CLS-compliant.
C#
public struct DateTime
{
public static TimeSpan operator -(DateTime t1, DateTime
t2) { }
public static TimeSpan
Subtract(DateTime t1, DateTime t2) { }
}
The following table contains a list
of operator symbols and the corresponding alternative methods and operator
names.
C++
operator symbol
|
Name
of alternative method
|
Name
of operator
|
Not defined
|
ToXxx or FromXxx
|
op_Implicit
|
Not defined
|
ToXxx or FromXxx
|
op_Explicit
|
+ (binary)
|
Add
|
op_Addition
|
- (binary)
|
Subtract
|
op_Subtraction
|
* (binary)
|
Multiply
|
op_Multiply
|
/
|
Divide
|
op_Division
|
%
|
Mod
|
op_Modulus
|
^
|
Xor
|
op_ExclusiveOr
|
& (binary)
|
BitwiseAnd
|
op_BitwiseAnd
|
|
|
BitwiseOr
|
op_BitwiseOr
|
&&
|
And
|
op_LogicalAnd
|
||
|
Or
|
op_LogicalOr
|
=
|
Assign
|
op_Assign
|
<<
|
LeftShift
|
op_LeftShift
|
>>
|
RightShift
|
op_RightShift
|
Not defined
|
LeftShift
|
op_SignedRightShift
|
Not defined
|
RightShift
|
op_UnsignedRightShift
|
==
|
Equals
|
op_Equality
|
>
|
Compare
|
op_GreaterThan
|
<
|
Compare
|
op_LessThan
|
!=
|
Compare
|
op_Inequality
|
>=
|
Compare
|
op_GreaterThanOrEqual
|
<=
|
Compare
|
op_LessThanOrEqual
|
*=
|
Multiply
|
op_MultiplicationAssignment
|
-=
|
Subtract
|
op_SubtractionAssignment
|
^=
|
Xor
|
op_ExclusiveOrAssignment
|
<<=
|
LeftShift
|
op_LeftShiftAssignment
|
%=
|
Mod
|
op_ModulusAssignment
|
+=
|
Add
|
op_AdditionAssignment
|
&=
|
BitwiseAnd
|
op_BitwiseAndAssignment
|
|=
|
BitwiseOr
|
op_BitwiseOrAssignment
|
,
|
None assigned
|
op_Comma
|
/=
|
Divide
|
op_DivisionAssignment
|
--
|
Decrement
|
op_Decrement
|
++
|
Increment
|
op_Increment
|
- (unary)
|
Negate
|
op_UnaryNegation
|
+ (unary)
|
Plus
|
op_UnaryPlus
|
~
|
OnesComplement
|
op_OnesCo
|
Security in Class Libraries
.NET Framework 1.1
Protecting Objects with Permissions
Permissions are defined to help protect specific resources. A class library that performs operations on protected resources must be responsible for enforcing this protection. Before acting on any request on a protected resource, such as deleting a file, class library code first must check that the caller (and usually all callers, by means of a stack walk) has the appropriate delete permission for the resource. If the caller has the permission, the action should be allowed to complete. If the caller does not have the permission, the action should not be allowed to complete and a security exception should be raised. Protection is typically implemented in code with either a declarative or an imperative check of the appropriate permissions.It is important that classes protect resources, not only from direct access, but from all possible kinds of exposure. For example, a cached file object is responsible for checking for file read permissions, even if the actual data is retrieved from a cache in memory and no actual file operation occurs. This is because the effect of handing the data to the caller is the same as if the caller had performed an actual read operation.
Fully Trusted Class Library Code
Many class libraries are implemented as fully trusted code that encapsulates platform-specific functionality as managed objects, such as COM or system APIs. Fully trusted code can expose a weakness to the security of the entire system. However, if class libraries are written correctly with respect to security, placing a heavy security burden on a relatively small set of class libraries and the core runtime security allows the larger body of managed code to acquire the security benefits of these core class libraries.In a common class library security scenario, a fully trusted class exposes a resource that is protected by a permission; the resource is accessed by a native code API. A typical example of this type of resource is a file. The File class uses a native API to perform file operations, such as a deletion. The following steps are taken to protect the resource.
- A caller
requests the deletion of file
c:\test.txt
by calling the File.Delete Method. - The Delete
method creates a permission object representing the
delete c:\test.txt
permission. - The File class's code checks all callers on the stack to see if they have been granted the demanded permission; if not, a security exception is raised.
- The File class asserts FullTrust in order to call native code, because its callers might not have this permission.
- The File class uses a native API to perform the file delete operation.
- The File class returns to its caller, and the file delete request is completed successfully.
Precautions for Highly Trusted Code
Code in a trusted class library is granted permissions that are not available to most application code. In addition, an assembly might contain classes that do not need special permissions but are granted these permissions because the assembly contains other classes that do require them. These situations can expose a security weakness to the system. Therefore, you must be take special care when writing highly or fully trusted code.Design trusted code so that it can be called by any semi-trusted code on the system without exposing security holes. Resources are normally protected by a stack walk of all callers. If a caller has insufficient permissions, attempted access is blocked. However, any time trusted code asserts a permission, the code takes responsibility for checking for required permissions. Normally, an assert should follow a permission check of the caller as described earlier in this topic. In addition, the number of higher permission asserts should be minimized to reduce the risk of unintended exposure.
Fully trusted code is implicitly granted all other permissions. In addition, it is allowed to violate rules of type safety and object usage. Independent of the protection of resources, any aspect of the programmatic interface that might break type safety or allow access to data not normally available to the caller can lead to a security problem.
Performance
Security checks involve checking the stack for the permissions of all callers. Depending upon the depth of the stack, these operations have the potential to be very expensive. If one operation actually consists of a number of actions at a lower level that require security checks, it might greatly improve performance to check caller permissions once and then assert the necessary permission before performing the actions. The assert will stop the stack walk from propagating further up the stack so that the check will stop there and succeed. This technique typically results in a performance improvement if three or more permission checks can be covered at once.Summary of Class Library Security Issues
- Any class library that uses protected resources must ensure that it does so only within the permissions of its callers.
- Assertion of permissions should be done only when necessary, and should be preceded by the necessary permission checks.
- To improve performance, aggregate operations that will involve security checks and consider the use of assert to limit stack walks without compromising security.
- Be aware of how a semi-trusted malicious caller might potentially use a class to bypass security.
- Do not assume that code will be called only by callers with certain permissions.
- Do not define non-type-safe interfaces that might be used to bypass security elsewhere.
- Do not expose functionality in a class that allows a semi-trusted caller to take advantage of the higher trust of the class.
See Also
Design Guidelines for Class Library Developers | Writing Secure Class Libraries | Code Access Security | Security and Culture-Aware String Operations
Asynchronous programming is a
feature supported by many areas of the common language runtime, such as Remoting,
ASP.NET,
and Windows Forms. Asynchronous programming is a core concept in the .NET
Framework. This topic introduces the design pattern for asynchronous
programming.
The philosophy behind these
guidelines is as follows:
- The client should decide whether a particular call should be asynchronous.
- It is not necessary for a server to do additional programming in order to support its clients' asynchronous behavior. The runtime should be able to manage the difference between the client and server views. As a result, the situation where the server has to implement IDispatch and do a large amount of work to support dynamic invocation by clients is avoided.
- The server can choose to explicitly support asynchronous behavior either because it can implement asynchronous behavior more efficiently than a general architecture, or because it wants to support only asynchronous behavior by its clients. It is recommended that such servers follow the design pattern outlined in this document for exposing asynchronous operations.
- Type safety must be enforced.
- The runtime provides the necessary services to support the asynchronous programming model. These services include the following:
- Synchronization primitives, such as critical sections and ReaderWriterLock instances.
- Synchronization constructs such as containers that support the WaitForMultipleObjects method.
- Thread pools.
- Exposure to the underlying infrastructure, such as Message and ThreadPool objects.
No comments:
Post a Comment