Aviva Solutions C # Code Writing Guidelines

I present to you the translation of the document " Coding Guidelines for C # 3.0, 4.0 and 5.0 ".

The goal of this list of rules is to try to set standards for writing C # code that are both convenient and practical. Of course, we practice what we have created. These rules are one of those standards that underlie our daily work at AvivaSolutions. Not all of these rules have a clear rationale. Some of them are simply accepted as standards. VisualStudio

Static Code Analyzer (also known as FxComp) and StyleCopcan automatically apply many of the coding and design rules by analyzing compiled assemblies. You can configure them so that the analysis is done at compile time or is an integral part of continuous or daily builds. This document simply adds additional rules and recommendations, but its supporting site www.csharpcodingguidelines.com provides a list of code analysis rules that are necessary depending on which code base you are working with.

Why should I use this?


Some perceive the standards for writing code as some restrictions that infringe on the freedom of creativity. Nevertheless, this approach is justified and tested for many years. Why? Because not every developer knows that:

  • It takes 10 times longer to figure out the code than to change it
  • Not every developer knows the intricacies of using basic constructs in C #
  • Not everyone knows which .NET Framework conventions to adhere to, for example, when using IDisposableor LINQ with its deferred execution
  • Not everyone knows how private solutions to a problem can affect performance, security, support for multiple languages, etc.
  • Not every developer can understand the beautiful, but abstract code of another developer

Basic principles


There are many unobvious things that I came across in my work as a consultant and which deserve mention in at least one recommendation for writing code. Unfortunately, the scope of this guide should be within reasonable limits. However, whatever some novice developers might think, if I did not mention something in this document, this does not mean that it does not deserve attention.

Based on the foregoing, you should understand that this document is not able to cover all the situations that you may encounter. In any controversial case, you should refer to the basic principles that apply in any situation, regardless of context. These include:

  • The Principle of Least Surprise . "The rule of least surprise." You should choose the most obvious solution for your tasks, so as not to confuse other developers and make the code more understandable.
  • Keep It Simple Stupid (or KISS). Literally - "Make it easier, dumbass." This principle states that to solve the tasks set, it is necessary to choose the simplest solution
  • You Ain't Gonne Need It (or YAGNI). “You won’t need it.” It says: “Work on solving current problems, do not write code just because you think it will be useful to you in the future (can you predict the future?)”
  • Don't Repeat Yourself (or DRY). "Do not repeat." This principle encourages you to refrain from duplicating code, while not forgetting the “ heuristic rule of three

Despite the fact that the code may look very good, see if it is understandable to other developers, does it have the behavior that you should expect from it, is it trying to solve the problems that may arise in the future. If this is not the case, then you should consider refactoring.

How do i get started?


  • Ask all developers to carefully read this document at least once. This should give them an understanding of what principles it contains.
  • Make sure you have at hand several printed copies of the short links to this manual.
  • Include the most significant rules in your Project Checklist, check for compliance with the remaining rules during Peer Review
  • Decide which static analysis rules apply to your project and write them somewhere. For example, publish them on your TFS team site or create a standard Visual Studio rule set
  • Enrich your custom code analysis vocabulary with terms, names, and concepts specific to your company or business. If you do not, the static analyzer will display warnings on structures that will not be included in its internal dictionary
  • Set VisualStudio to verify that the selected static analysis rules are followed as part of the release build. Then they will not interfere with development and debugging, but can be started when switching to the release configuration
  • Add check points to the project checklist to follow the recommendations to be sure that all new code matches them, or use the appropriate checkin policy if you want any code to be checked for compliance with the rules before sending it to the repository
  • ReSharper has an intelligent code inspector that, with some configuration, already supports many of the requirements of this guide. It will automatically highlight any code that does not comply with the naming rules (for example, pascal naming style or camel notation), find the dead code and do any other checks. One mouse click (or hotkey combination) will be enough to fix this.
  • ReSharper, among other things, has a File Structure window that gives a general idea of ​​the members of your class or interface and allows you to easily move them with a simple drag and drop
  • Using GhostDoc , you can generate XML comments using keyboard shortcuts. The whole charm is that it allows you to accurately follow the style of MSDN documentation. However, do not abuse this tool and use it only as a starting

Why did you create this?


The idea came in 2002 when Vick Hartorg (Philips Medical System) and I was tasked with writing coding standards for C # 1.0. Since that time, I regularly added, deleted, and changed the rules based on experience, peer reviews, and new plugins introduced in Visual Studio 2010. In addition, after reading Robert Martin 's book “Clean Code: Creating, Analyzing, and Refactoring”I fired up with his ideas and decided to include some of them as rules. I want to note that this document is by no means a substitute for his book. I sincerely recommend reading his book in order to firmly grasp the principles that underlie his recommendations. In addition, I decided to supplement the principles of writing code with some design principles. They are too important to be ignored, and have a big impact on achieving high quality code.

Are these guidelines standards?


This document does not state that these recommendations must be strictly adhered to. Nor does it argue that these recommendations are more important than the rest. However, we recommend that you decide what recommendations are important, what deviations from the standard can be allowed in the project, who will be the consultant in case of doubt, and which layout to use for the source code. Obviously, it is you who must make these decisions before embarking on a real job.

To help you make your choice, I have assigned the importance of each recommendation:

- A rule that you should never neglect and that applies to all situations.
- It is highly recommended that you comply with this rule.
- It is recommended to comply with this rule, but it does not apply to all situations.

In conclusion, I want to say that there is no need for all the generated code to comply with the standards. However, if it is possible to modify the templates used to generate the code, try to make sure that they create the code that best suits these recommendations.

Feedback and disclaimer


This document was drafted largely thanks to the great contributions of community members, blog articles, online discussions, and many years of development in C #. If you have questions, comments and suggestions, write to me at dennis.doomen@avivasolutions.nl or on Twitter http://twitter.com/ddoomen . I will try to regularly review and republish this document in accordance with new ideas, experiences and comments.

I want to note that these recommendations only reflect my vision of the correct C # code. Aviva Solutions is not liable for any direct or indirect loss arising from the application of the standards described in this document.
Permission is granted to copy, adapt and distribute this document and short references to this manual for non-commercial purposes and for internal use. But you cannot distribute this document, publish or distribute any adapted copies of this document for commercial purposes without first obtaining written permission from Aviva Solutions.

Class Design Guidelines


AV1000 A class or interface must have a single purpose A

class or interface must have a single purpose within the system in which it is used. Typically, a class serves one of the purposes: either it describes a type, for example, email or ISBN (international standard book number), or is an abstraction of some business logic, or it describes a data structure, or is responsible for the interaction between other classes. He should never combine these tasks in himself. This rule is known as the Single Responsibility Principle , one of the principles of SOLID.

Tip : A class with the word “And” in the name is a clear violation of this rule.
Tip : To interact between classes, usedesign patterns . If you are unable to apply any of the patterns to the class, perhaps it takes on too much responsibility.
Note : If you create a class that describes a type, you can greatly simplify its use by making it immutable.

AV1001 Create new instances of the class using the constructor so that as a result you get a completely ready-to-use object .

The created object should not need to set additional properties before use for whatever purpose it would not be planned to apply. Moreover, if the constructor needs more than three parameters (which violates the AV1561 rule), it is possible that the class assumes too much responsibility (violation of the AV1000 rule).

AV1003 The interface should be small and should focus on one task.

The interface must have a name that clearly describes its purpose or the role that it plays in the system. Do not combine loosely coupled elements into one interface just because they belong to the same class. Configure interfaces based on the functionality for which the called methods are responsible or based on the specific task that this interface performs. This rule is better known as the Interface Segregation Principle .

AV1004 Use an interface rather than a base class to support multiple implementations

If you want to set the extension point of your class, expose it as an interface, not a base class. You will not want to force users of this extension point to make their implementations based on a base class that may behave in an undesirable way. However, for their convenience, you can create a default implementation (abstract class) that can serve as a starting point.

AV1005 Use an interface to implement loosely coupled between classes

Interfaces is an excellent tool to implement loosely coupled between classes.

  • They help avoid bidirectional connectivity.
  • They make it easy to replace one implementation with another.
  • They allow you to replace an inaccessible external service or resource with a temporary stub for use in a non-working environment
  • It allows you to replace the current implementation with a dummy one during unit testing.
  • Using the framework for implementing dependency injection, you can collect in one place the logic of class selection depending on the requested interface

AV1008 Avoid Static Classes

With the exception of static classes that are used to create extension methods, static classes very often lead to bad code. In addition, they are very difficult, if at all possible, to test in isolation until you resort to some very sophisticated tools.

Note : If you really need a static class, mark it as static. In this case, the compiler will prohibit the creation of instances of this class and initialize your class before the first call to it. This saves you from having to use a private constructor.

AV1010 Do not hide inherited elements behind the new keyword

This is not only contrary to polymorphism- one of the most important principles of object-oriented programming, but also makes child classes difficult to understand. Consider the following two classes:

public class Book 
{ 
    public virtual void Print() 
    {      			
        Console.WriteLine("Printing Book"); 
    } 
} 
public class PocketBook : Book 
{ 
    public new void Print() 
    { 
        Console.WriteLine("Printing PocketBook"); 
    } 
}

In this case, the class will not have the behavior that you would expect from it when using it:

PocketBook PocketBook = new PocketBook ();
pocketBook.Print ();  // Выведет "Printing PocketBook"
((Book)pocketBook).Print(); // Выведет  "Printing Book"

It is shown here that the method Printhas different behavior depending on whether it is called through a reference to the base class or was called as a method of a derived class.

AV1011 Functions that use the base type should be able to use subtypes of the base type without being aware of it.

In other words, the behavior of inherited classes should not contradict the behavior defined by the base class, that is, the behavior of the inherited classes should be expected for code using a variable of the base type . The most famous example of a violation of this rule is an exception NotImplementedExeptionwhen it occurs when you override a base class method.

Note: This principle is also known as the Barbara Lisk Substitution Principle, one of the SOLID principles .

AV1013 Do not refer to derived classes from the base class

Having dependencies in the parent class on its child classes violates the principles of object-oriented programming and prevents other developers from inheriting from your base class.

AV1014 The object must have limited knowledge about other objects that are not directly related to this object.

If your code resembles the code below, then you are violating the Law of Demeter .

someObject.SomeProperty.GetChild().Foo();

An object should not open access to the classes on which it depends, because user objects may incorrectly use properties and methods to gain access to objects behind it. By doing so, you allow the called code to integrate with the class you are using. Thus, you limit the possibility of replacing one implementation with another in the future.

Note : Use a class that implements a fluid interface (Fluent Interface), it may seem a violation of this rule. But the called methods simply pass the call context to the next link. Thus, this does not cause controversy.
An exception: When using inversion of control and frameworks, dependency injection often requires dependencies to be exposed as public properties. As long as these properties are not used for anything other than implementing dependency injection, I would not consider this as a violation of the rule.

AV1020 Avoid

Bidirectional Dependence Bidirectional dependence means that two classes are aware of each other's public methods or are dependent on each other's internal behavior. Refactoring or replacing one of these two classes requires changes in both classes and can entail a lot of unexpected work. The most obvious solution is to create an interface for one of these classes and use dependency injection.

An exception: Domain Models used in Domain Driven Designs can use bidirectional dependencies that describe real-world associations. In such cases, I try to make sure that they are really necessary, and as far as possible I try to avoid them.

AV1025 Classes must have state and behavior

If your repository contains many classes that are used only to describe data, then most likely you have several classes (static) that contain a lot of logic for processing this data (see rule AV1008). Use the principles of object-oriented programming according to the recommendations in this section, move your data processing logic to the data that it uses.

Exception : The only exception to this rule are classes used to transfer data between application subsystems, also called Data Transfer Objects , or classes that serve as a wrapper for method parameters.

Class Member Design Guidelines


AV1100 Class properties must be able to be set in any order.

Properties must not depend on other properties. In other words, there should be no difference in which property we set in the first place. For example, first DataSouse, then DataMemberor vice versa.

AV1105 Use a method instead of a property

Use methods instead of a property if:

  • More expensive work is done than setting the field value
  • If the property is a conversion. For example, the Object.ToStringmethod
  • If the property returns different values ​​for each call, even if the arguments are not changed. For example, a method NewGuidwill return a new value each time.
  • If using a property causes a side effect. For example, a change in internal state that is not directly related to a property (this violates command-query responsibility segregation (CQRS) )

Exception : Filling the internal cache or implementing lazy-loading are good exceptions to this rule.

AV1110 Do not use mutually exclusive properties.

If you have properties that cannot be used at the same time, this means that they are two mutually exclusive concepts. Even if these concepts can have some common logic and state, it is obvious that they have different rules that do not fit together.

Violation of this rule can often be found in the domain model, when properties encapsulate all kinds of conditional logic, containing mutually exclusive rules. This often causes a wave effect and maintaining such code becomes more time consuming.

AV1115A method or property should have a single purpose.

Just like a class (see rule AV1000), each method should have one area of ​​responsibility.

AV1125 Do not expose state describing

objects with static members Stateful objects are objects that contain a lot of properties and logic that these properties encapsulate. If you expose such an object through a static property or method of another object, it will be difficult to refactor and unit test classes that depend on an object with a state. In general, using the design described above is a great example of violating the many recommendations described in this chapter.

A classic example is the propertyHttpContext.Currentin ASP.NET. Many people look at the class HttpContextas the source of a lot of dirty code. In fact, one of the testing rules - isolate the ugly stuff (Isolate the Ugly Stuff) - often belongs to this class.

AV1130 Return IEnumerableor ICollectioninstead of a specific collection

If you do not want users to be able to modify the collection, do not return an array, sheet, or other collection class directly. Instead, return IEnumerable, or if the user needs to know the number of elements in the collection ICollection.

Note : If you use .Net 4.5, you can also use IReadOnlyCollection, IReadOnlyListor . AV1135IReadOnlyDictionary

Properties, methods, or arguments that are a string or a collection should never be equal. null

Returning nullas a result of a method execution may come as a surprise to the user. Always return an empty collection or empty string instead of a null reference. In addition, it saves you the trouble of clogging your code with additional checks for, nullor, even worse, use string.IsNullOrEmpty().

AV1137 Define parameters as specific as possible

If a class element requires part of the data of another class as parameters, define the data types of these parameters as specific as possible and do not take the entire object as a parameter. For example, consider a method that requires passing a connection string described in a central interface as a parameter IConfiguration. Instead of taking the entire object that implements this interface as a parameter, pass only the connection string. This will not only allow you to reduce the number of dependencies in the code, but also improve its maintainability in the long run.

AV1140 Use types specific to your subject area instead of primitives

Instead of using strings, integers and fractional numbers to represent specific types such as ISBN (International Standard Book Number), email address or monetary amount, create objects for each type that will include both the data itself and the validation rules, which will apply to them. By doing so, you can avoid multiple implementations of the same business rules. This will improve the maintainability of your code and reduce the number of bugs.

Various design guidelines


AV1200 Throw an exception instead of returning a status message A

code base that uses the returned status message to determine whether the operation completed successfully or not often has nested if expressions scattered throughout the code. Often, users forget to check the return value. Structured exception handling has been introduced to allow you to throw exceptions and catch or replace them at a higher level. On most systems, it is quite common practice to throw exceptions whenever an unexpected situation occurs.

AV1202 Provide a complete and meaningful exception message

The message should explain what led to the exception and clearly describe what needs to be done to avoid it in the future.

AV1205 Generate as specific an exception as possible.

For example, if the method accepted as an input parameter null, you should generate ArgumentNullExceptioninstead of ArgumentExceptionits base type.

AV1210 Do not ignore the error by handling general exceptions

Do not ignore errors by handling general exceptions, such as Exception, SystemExceptionand others in the application code. Only the top-level error handler should catch general exceptions in order to log and correctly terminate the application.

AV1215Handle exceptions in asynchronous code properly

When you throw or handle exceptions in code that uses async/awaitor Task, remember the following two rules:

  • Exceptions that occur within async/awaitand within blocks Taskapply to a task that expects these blocks to complete.
  • Exceptions that occurred in the code preceding async/awaitand Taskapply to the calling code


AV1220 Always check the event handler delegate for an null

Event that has no subscriptions, equal null. Thus, before it is called, make sure that the list of delegates representing this event is not equal null. Also, to avoid conflicts when changing from parallel threads, use a temporary variable to avoid changing at the same time.

event EventHandler Notify; 
void RaiseNotifyEvent(NotifyEventArgs args) 
{ 
    EventHandler handlers = Notify; 
    if (handlers != null) 
    { 
        handlers(this, args);    
    } 
} 

Hint : You can make the delegate list not be completely empty. Just declare an empty delegate as shown below:

event EventHandler Notify = delegate {}; 

AV1225 Use a protected virtual method to process each event. Following

this recommendation will allow derived classes to handle the base class event by overriding the protected method. The name of the protected virtual method must be the same as the name of the event, but with a prefix On. For example, a protected virtual method for an event with a name TimeChanged should be named OnTimeChanged.

Note : Derived classes that override the protected virtual method are not required to call the base class implementation. The base class should continue to work correctly, even if its implementation is not called.

AV1230 Using Property Change Notification Events

A property change notification event should have a name like PropertyChangedwhere it Propertyshould be changed to the name of the property that this event is associated with.

Note : If your class has many properties that require appropriate events, try implementing an interface instead INotifyPropertyChanged. It is often used in Presentation Model and Model-View-ViewModel patterns .

AV1235 Do not send nullas an argument when triggering an event

Often an event handler is used to handle similar events from multiple senders. In this case, the argument passed is used to convey the context of the event call. Always send a link to the context (usually this) when the event is called. Also, do not dispatch an nullevent if it has no data. If the event has no data, send EventArgs.Emptyinstead null.

Exception : For static events, the argument passed must be null.

AV1240 Use general restrictions if possible

Instead of casting and converting a type from a specific to a general one and vice versa, use a keyword whereor operator asto cast the object to a specific type. For instance:

class SomeClass 
{}  
// Неправильно
class MyClass 
{ 
    void SomeMethod(T t) 
    { 
        object temp = t; 
        SomeClass obj = (SomeClass) temp; 
    } 
} 
// Правильно
class MyClass where T : SomeClass 
{ 
    void SomeMethod(T t) 
    { 
        SomeClass obj = t; 
    } 
}

AV1250 Calculate the result of a LINQ query before returning it.

Look at the following code:

public IEnumerable GetGoldMemberCustomers() 
{ 
    const decimal GoldMemberThresholdInEuro = 1000000;  
    var q = from customer in db.Customers 
            where customer.Balance > GoldMemberThresholdInEuro 
            select new GoldMember(customer.Name, customer.Balance);  
    return q;          
} 

Since LINQ queries use deferred execution, the return q, oddly enough, will return an expression tree representing the above query. Whenever the user calculates the result using foreachor something similar, the entire query is re-executed, creating new instances each time GoldMember. As a result, you cannot use the operator ==to compare different instances GoldMember. Instead, always explicitly compute the result of the LINQ query using ToList(), ToArray()or similar methods.

Recommendations for improving code maintainability


AV1500 The method should not have more than 7 declarations

A method that includes more than 7 declarations will most likely do too much or take too much responsibility. In addition, human memory requires that the method be short. She is not able to hold in herself at the same time more things in order to accurately analyze and understand what a particular piece of code does. Divide the method into several small ones with a clear purpose, and give them names that will accurately indicate what they are doing. At the same time, pay attention to the fact that the algorithm of this part of the program remains clear for understanding.

AV1501 Create all members of a class private, and types internalby default

To make a more informed decision about which elements should be available to other classes, first of all, limit their scope as much as possible. Then carefully consider which members or types are really worth making public.

AV1502 Avoid Double Negation

Although a property customer.HasNoOrderhas a right to exist, avoid using it with negation. For instance:

bool hasOrders = !customer.HasNoOrders;

Double negation is more difficult to understand than simple expression, and people tend to get confused in it.

AV1505 The name of the assembly in its name should go after the name of its namespace.

All DLLs should be named according to the pattern Company.Component.dll, where Companyis the name of your company, and Componentis the name of one or more namespace separated by dots. For instance:

AvivaSolutions.Web.Controls.dll

An example is the union of a class group in a namespace AvivaSolutions.Web.Binding, which includes some kind of assembly. According to this recommendation, this assembly should be named AvivaSolutions.Web.Binding.dll.

Exception : If you decide to link classes from different unrelated namespaces into one assembly, add the suffix Core to its name. However, do not use this suffix in namespace names. For example: AvivaSolutions.Consulting.Core.dll.

AV1506 Name the source code files according to the type of data it contains.

Use pascal notation to name files and do not use underscores.

AV1507 Limit contents of source file to one data type

Exception: Nested types, for obvious reasons, must be part of the same file.

AV1508 The name of a file with source code that contains a partial data type should reflect the purpose of this part.

When partial types are used and parts are divided into files, the name of each file should be logically divided into two parts. The first part is the type name. The second is the role this fragment plays in the type. For instance:

// В файле MyClass.cs 
public partial class MyClass 
{...} 
// В файле MyClass.Designer.cs 
public partial class MyClass 
{...}

AV1510 Use usinginstead of specifying a full reference to a type from another namespace

Do not use a full reference to a type from another namespace to prevent naming conflicts. For example, do not do this:

var list = new System.Collections.Generic.List();

It’s better to do this:

using System.Collections.Generic; 
var list = new List();

If you need to avoid naming conflicts, use the directive usingto create an alias for a namespace or type:

using Label = System.Web.UI.WebControls.Label;

AV1515 Do not use magic numbers.

Do not use literal values, numbers, or lines in your code for anything other than declaring constants. For example:

public class Whatever
{
    public static readonly Color PapayaWhip = new Color(0xFFEFD5);
    public cons tint MaxNumberOfWheels = 18;
} 

Lines intended for logging or tracing are an exception to this rule. Literal values ​​are allowed to be used only when their meaning is clear from the context and it is not planned to change them. For instance:

mean = (a + b) / 2; // среднее арифметическое
WhaitMilliseconds(waitTimeInSeconds * 1000); // тут тоже все понятно

If the value of one constant depends on the value of another, indicate this in your code.

public class SomeSpecialContainer
{
    public const int MaxItems = 32;
    public const int HighWaterMark = 3 * MaxItems / 4;     // 75%
}

Note : Enumerations can often be used as storage for character constants.

AV1520 Use varonly when the type of the variable is obvious

Use varonly if the variable is assigned the result of a LINQ query or if the type of the variable is obvious and use varwill increase code readability. For example, you should not do this:

var i = 3;                                 // Что за тип? int? uint? float?
var myfoo = MyFatoryMethod.Create(“arg”);  // Не понятно, какой тип имеет 
					   // класс или интерфейс. Кроме того,
					   // тяжело изменять код, который работает 
					   // c этой переменной, если исходный класс
					   // вам недоступен

Instead, use varas in the examples below:

var q = from order in orders where order.Items > 10 and order.TotalValue > 1000; 
var repository = new RepositoryFactory.Get(); 
var list = new ReadOnlyCollection();

In all three examples, the type of variable assignment is obvious. For more information on using var, read Eric Lippert 's article , Using and Abusing Implicit Typing .

AV1521 Declare and initialize variables as late as possible.

Avoid the C and VisualBasic languages ​​when all variables are declared at the beginning of a block. Declare and initialize each variable only when it is needed.

AV1522 Assign the value of each variable in a separate declaration

Never do this:

var result = someField = GetSomeMethod();

AV1523 Prefer the initializers of objects and collections to the separate setting of properties and the separate addition of new objects to the collection

Instead of this construction:

var startInfo = new ProcessStartInfo(“myapp.exe”);
startInfo.StandardOutput = Console.Output; 
startInfo.UseShellExecute = true;

Use the object initializer :

var startInfo = new ProcessStartInfo(“myapp.exe”) 
{ 
    StandardOutput = Console.Output, 
    UseShellExecute = true 
};

Instead of creating a collection, this way:

var countries = new List(); 
countries.Add(“Netherlands”); 
countries.Add(“United States”);

Use the collection initializer :

var countries = new List { “Netherlands”, “United States” };

AV1525 Do not make explicit comparisons with trueor false

Comparing a boolean with trueor false- this is usually a bad programming style. As an example:

while (condition == false)  // неправильно, плохой стиль 
while (condition != true) // тоже неправильно 
while (((condition == true) == true) == true) // где ты остановишься? 
while (condition) // OK  

AV1530 Do not change the loop variable foreither foreachinside the loop body

Updating the loop variable inside the loop body causes the code to become confused. Especially if the variable changes in more than one place. This rule also applies to the loop foreach, although after the iteration the enumerator will detect a collection change and throw an exception.

for (int index = 0; index < 10; ++index) 
{ 
    if (some condition)  
    { 
        index = 11; // Неправильно! Вместо этого используйте ‘break’ или ‘continue’. 
    }  
}

AV1532 Avoid nested loops

Methods containing nested loops are more difficult to understand than those that contain only one loop. In fact, in most cases, loops can be replaced with a much smaller LINQ query, which uses the keyword fromtwice or more to combine the data.

AV1535 Always use design if, else, while, for, foreachand casewith braces

Please note that this will also help to avoid possible confusion with constructions like this:

if (b1) if (b2) Foo(); else Bar();  // к какому ‘if’ относится ‘else’?

It’s better to do this:

if (b1) 
{ 
    if (b2) 
    { 
        Foo(); 
    } 
    else 
    { 
        Bar(); 
    } 
}

AV1536 Always use a block default at the end of a construct. switch/case

If the block defaultis empty, add an explanatory comment. In addition, if this block should not be reachable, generate it when called InvalidOperationExceptionto detect future changes in which none of the blocks casewill be reached. Following this recommendation will allow you to write cleaner code because all execution scripts have already been thought out.

void Foo(string answer) 
{ 
    switch (answer) 
    { 
        case "no": 
            Console.WriteLine("You answered with No"); 
            break; 
        case "yes": 
            Console.WriteLine("You answered with Yes"); 
            break; 
        default: 
            // Not supposed to end up here. 
            throw new InvalidOperationException("Unexpected answer " + answer); 
   } 
}

AV1537 End each block with an if-else-ifad else

For example:

void Foo(string answer) 
{ 
    if (answer == "no") 
    { 
        Console.WriteLine("Вы ответили Нет"); 
    } 
    else if (answer == "yes") 
    { 
        Console.WriteLine("Вы ответили Да"); 
    } 
    else 
    { 
        // Что должно случиться, когда этот блок выполнится? Игнорировать это? 
        // Если нет, то  сгенерировать исключение InvalidOperationException. 
     } 
}

AV1540 Try to avoid multiple announcements. return

One input - one exit point, this principle sounds like this. It allows you to keep clear the progress of the method. Moreover, if the method is very small and meets the recommendations of AV1500, then several announcements returnmay be relevant and will improve the readability of the code. For example, if a method returns a logical value, it is more convenient to use two declarations returninstead of the logical variable that the method will return and to which values ​​will be assigned as it is executed.

AV1545 Do not use a block if-elseinstead of a simple (conditional) assignment

Express your intentions directly. For example, instead:

bool pos;
if (val > 0) 
{ 
    pos = true; 
} 
else 
{ 
    pos = false; 
}

Do this:

bool pos = (val > 0);   // инициализация

Instead:

string result; 
if (someString != null) 
{ 
    result = someString; 
} 
else 
{ 
    result = “Unavailable”; 
}

Write:

return someString ?? “Unavailable”;

AV1547 Encapsulate a complex expression in a method or property

Consider the following example:

if (member.HidesBaseClassMember && (member.NodeType != NodeType.InstanceInitializer)) 
{ 
    // что-то делаем
}

To understand what this code does, you will have to delve into its details and foresee all options for its execution. Of course, you can add an explanatory comment before this code, but it’s better to replace the complex expression with a method whose name will speak for itself.

if (NonConstructorMemberUsesNewKeyword(member)) 
{ 
    // что-то делаем
}
private bool NonConstructorMemberUsesNewKeyword(Member member) 
{ 
 	return  
            (member.HidesBaseClassMember &&  
    	    (member.NodeType != NodeType.InstanceInitializer) 
}

If you need to change this method, you still have to understand how it works. But now it’s much easier to understand the code that calls it.

AV1551 Call the most overloaded method from other overloads.

This rule applies only to those methods that are overloaded with optional arguments. Take a look at the example below:

public class MyString 
{ 
    private string someText;  
    public MyString(string text) 
    { 
        this.someText = text; 
    } 
    public int IndexOf(string phrase) 
    { 
        return IndexOf(phrase, 0, someText.Length); 
    } 
    public int IndexOf(string phrase, int startIndex) 
    { 
        return IndexOf(phrase, startIndex, someText.Length - startIndex ); 
    } 
    public virtual int IndexOf(string phrase, int startIndex, int count) 
    { 
        return someText.IndexOf(phrase, startIndex, count); 
    } 
}

The class MyStringprovides three method overloads IndexOf, while two of them simply call another with a large number of parameters. Note that this rule applies to class constructors. Implement the most overloaded constructor and call it from other overloads using the operator this(). It should also be noted that parameters with the same names must follow in the same order in all overloads.

Important : If you want the behavior of classes to be changed by overriding these methods, then declare the most overloaded method as protected virtualwhich is called by all overloads.

AV1553 Use optional arguments only to replace overloads

The only valid reason to use the optional C # 4.0 arguments is to replace the example from rule AV1551 with a single method like this:

public virtual int IndexOf(string phrase, int startIndex = 0, int count = 0) 
{ 
    return someText.IndexOf(phrase, startIndex, count); 
} 

If the optional parameter is a reference type, then it can have only the default value null. But, as we know string, listand collectionsshould never be equal null(according to the rule AV1135). Therefore, you should use the overloaded method instead.

Note : The compiler copies the value of the optional parameters to the call location. Therefore, a change in the default value for optional parameters must be accompanied by recompilation of the calling code.
Note : When an interface method defines an optional parameter, its default value is not considered during overload resolution until you invoke an implementation of this method through the interface. See articleEric Lippert for more info.

AV1555 Avoid Using

Named Arguments C # 4.0 named arguments were created to make it easier to invoke COM components that are known to offer tons of optional parameters. If you need named arguments to improve readability of a call to a method, most likely this method does too much and needs to be refactored.

The only exception where named arguments improve readability is when the constructor of an object is called, as in the example below:

Person person = new Person 
( 
    firstName: "John",  
    lastName: "Smith",  
    dateOfBirth: new DateTime(1970, 1, 1) 
);

AV1561 Do not allow a method or constructor to accept more than three parameters.

If your method or constructor accepts more than three parameters, use a structure or class to encapsulate them according to the pattern specification . In general, the smaller the number of parameters, the easier it is to understand the method. In addition, unit testing of a method with many parameters requires many scenarios for testing.

AV1562 not use refand outthe parameters

they make the code less clear, and create the prerequisites for error. Instead, return compound objects as the result of the function.

AV1564 Do not create methods that take a boolean as a parameter

Take a look at the following method:

public Customer CreateCustomer(bool platinumLevel) {}

At first glance, everything looks great, but when you use this method, the meaning of a logical variable will completely lose its clarity:

Customer customer = CreateCustomer(true);

Typically, if a method accepts a Boolean flag as a parameter, then it does more than one thing and needs refactoring to split into two or more methods. An alternative solution is to replace the flag with an enumeration.

AV1568 Do not use parameters as temporary variables.

Never use a parameter as an internal variable. Even if the type of the parameter may coincide with the type that you require, the name, as a rule, will not reflect the purpose of the temporary variable.

AV1570 Always check the result returned by the operator. as

If you use the operator asto bring an object to a specific interface, always check the result returned by it onnull. Failure to do so may result in exclusion NullReferenceExceptionat a much later stage of program execution if the object does not implement the required interface.

AV1575 Do not leave commented sections of code.

Never send commented code to the repository. Instead, use a task tracking system to keep track of what work needs to be done. No one subsequently guesses what this or that block of commented code is for. Was it temporarily commented out for testing? Was it copied as an example? Should I remove it?

Naming Guidelines


AV1701 Use American English.

All class members, parameters, and variables must be named using words from American English.

  • Choose an easy, readable, preferably grammatically correct name. For example, a HorizontalAlignmentmore readable name thanAlignmentHorizontal
  • Prefer readability to brevity. A property name is CanScrollHorizontallybetter than ScrollableX(referring to the X axis says nothing)
  • Avoid using names that conflict with programming language keywords

Exception : In most projects, you can use words and phrases from the subject area specific to this application, as well as names specific to your company. Visual Studio's static code analyzer will analyze the entire code, so you may need to add these terms to a custom code analysis dictionary .

AV1702 Use each notation for each language element.

Language elementNotationExample
Class structurePascalAppDomain
InterfacePascalIBusinessService
Enumeration (type)PascalErrorLevel
Enumeration (value)PascalFatalError
EventPascalClick
Private fieldCamel notationlistItem
Protected fieldPascalMainPanel
Constant fieldPascalMaximumItems
Constant local variableCamel notationmaximumItems
Read-only static fieldPascalRedValue
VariableCamel notationlistOfValues
MethodPascalToString
NamespacePascalSystem.Drawing
ParameterCamel notationtypeName
Type parametersPascalTView
PropertyPascalBackColor

AV1704 Do not include numbers in the names of variables, parameters, and types.

In most cases, only laziness can cause a lack of a clear and self-explanatory name.

AV1705 Do not use prefixes in field names.

For example, do not use g_or s_to distinguish between static and non-static fields. Usually, if it is difficult to distinguish local variables from class fields in a method, then this method is too cumbersome. Here are some examples of incorrect names: _currentUser, mUserName, m_loginTime.

AV1706 Do not use abbreviations

For example, use OnButtonClickinsteadOnBtnClick. Avoid using single characters in variable names such as “i” and “q”. Instead, use complete words such as “index” and “query”.

Exception : The use of a well-known abbreviation or generally accepted abbreviation from your subject area may be an exception to this rule. For example, use UIinstead UserInterface.

AV1707 Name class members, parameters, and variables according to their purpose, not type

  • Use a name that indicates the function that a member of the class performs. For example, the name is GetLengthbetter.GetInt
  • Do not use terms such as Enum, Classor Structin names
  • Variables referencing collections must have a plural name

AV1708 Name types using phrases from nouns or adjectives

Bad examples: SearchExamination(page to search for test results), Common(there is no noun at the end, the name does not explain the purpose) and SiteSecurity(although from a technical point of view everything is fine, the name says nothing about the purpose). Good examples are: BusinessBinder, SmartTextBoxand EditableSingleCustomer.

Do not include terms such as Utilityor in class names Helper. Classes with such names are usually static and are designed without taking into account the principles of object-oriented programming (see also rule AV1008).

AV1709 Use descriptive names when naming generic type parameters

  • Always add the prefix “T” to descriptive names of type parameters
  • Always use descriptive names, unless a single-letter name is fully understood without explanation. In this case, use the letter “T” as the name of the type parameter
  • It is recommended that the type parameter name specify restrictions imposed on the type parameter. For example, a parameter intended only for ISessionmay be calledTSession

AV1710 Do not repeat class name or listing in member names

class Employee 
{ 
    // Плохо!  
    static GetEmployee() {} 
    DeleteEmployee() {} 
    // Правильно
    static Get() {...} 
    Delete() {...} 
    // Тоже верно. 
    AddNewJob() {...} 
    RegisterForMeeting() {...} 
}

AV1711 Give the elements names that are similar to the elements of their associated .NET Framework classes.

NET developers are already used to the naming patterns that are used in the .NET Framework. Thus, following these patterns will help them understand your code faster. For example, if you define a class that implements collection, name removal methods element, its preparation and adding the number of elements names like Add, Removeand Countinstead of AddItem, Deleteor NumberOfItems.

AV1712 Avoid short names or names that may be confused with other names

Although from the technical point of view the following expression may look correct, it can easily mislead anyone who encounters it

bool b001 = (lo == l0) ? (I1 == 11) : (lOl != 101);

AV1715 Do not be lazy to give suitable names to properties

  • Name properties using nouns, phrases with nouns, or, in extreme cases, using phrases with adjectives
  • Name a property that has a logical type using affirmative phrases. For example, CanSeekinstead ofCantSeek
  • The names of the properties that have a logical type, try using the prefix Is, Has, Can, AllowsorSupport
  • Try giving the property the same name as its type. When you create a property that has an enumeration type, the property name may be the same as the enumeration type name. For example, if you have an enumeration CacheLevel, then a property that returns one of its values ​​should also have a nameCacheLevel

AV1720 Name methods using the verb-object conjunction. Name methods using the verb-object

conjunction. A good example is ShowDialog. A good name should give a hint what this method does and, if possible, why. Also do not use the word Andin the name of the method. This suggests that the method does more than one thing, which is a violation of the principle of single responsibility (AV1115).

AV1725 In names of names use proper names, names of modules (layers), verbs and words describing features of a given namespace

For example, the names of the following namespaces can serve as a good example:

AvivaSolutions.Commerce.Web 
NHibernate.Extensibility 
Microsoft.ServiceModel.WebApi 
Microsoft.VisualStudio.Debugging 
FluentAssertion.Primitives 
CaliburnMicro.Extensions

Note : Never allow namespaces to contain type names, but if it is a noun in its plural form, for example Collections, then this is usually valid.

AV1735 Use a verb or phrase with a verb in the name of the event.

Name the event with a verb or phrase with a verb. For example: Click, Deleted, Closing, Minimizing, and Arriving. An event announcement Searchmay look like the one below:

public event EventHandler Search;  

AV1737 Use –ingboth –edfor events that should occur before and after any other event.

For example, the event that precedes the closing of the window should be called Closing, and the event that occurs after it is closed is Closed. Do not use prefixes Beforeand Afteror any suffixes to identify these events.

Suppose you want to identify events that are related to the process of deleting an object. Give the events names Deletingand Deletedand avoid names like BeginDeleteand EndDelete. Name the events as follows:

  • Deleting: Occurs just before deleting an object
  • Delete: Occurs when an object must be deleted by an event handler.
  • Deleted: Will happen when the object is already deleted.

AV1738 Use a prefix Onin the name of the event handler It is

good practice to add a prefix Onto the name of the method that handles the event. For example, if a method handles an event Closing, then the name should be OnClosing.

AV1739 Use the underscore character for parameters of lambda expressions that do not matter.

If you use a lambda expression, for example, to subscribe to an event, and the current parameters of the event do not matter, use the following conventions to indicate this more specifically.

button.Click += (_, __) => HandleClick();

AV1745 Name the extension method groups in the class using the suffix. Extentions

If the name of the extension method conflicts with another element or extension method, you must add the class name prefix to the call. Adding them to the associated class with a suffix Extensionswill improve readability.

AV1755 Add suffixes Asyncor TaskAsyncto the names of asynchronous methods. A
general recommendation for methods that return Taskis to add a suffix to them Async. However, if a method with that name already exists, use the suffix instead TaskAsync.

Performance Recommendations


AV1800 Use Any()to check IEnbmerablefor emptiness

If a method or other item returns IEnumerableor another collection class that does not provide a property Count, use the extension method Any()instead Count()to check the collection for emptiness. If you use Count(), then you run the risk of reducing performance, because this will lead to iteration of the entire collection (for example, in the case of, a IQueryabledata request will be executed).

Note : If you are returning IEnumerablein order to prevent the return collection from being changed, as recommended in AV1130 rule, and you are working with .NET 4.5 and higher, try using the new read-only classes.

AV1820 Useasynconly for long-term and low-intensity tasks.

Using asyncwill not automatically start anything in the workflow, as it does Task.Run. Async, simply adds the necessary logic, which serves to enable the release of the current thread and return the result to the same thread after the completion of the asynchronous operation. In other words, use asynconly for I / O related operations.

AV1825 Use Task.Runfor high-intensity tasks

If you need to perform an operation related to the allocation of additional processor resources, use Task.Runto unload work on a thread from the thread pool. Just do not forget that you will have to manually return the result to your main thread.

AV1830Avoid using await/asyncwith Task.Wait

awaitwill not block the current thread, but simply inform the compiler about the need to build a state machine. However, it will Task.Waitblock the flow and may even lead to deadlocks (see AV1835).

AV1835 Beware of mutual blocking async/awaitin a single-threaded environment
Consider the following asynchronous method:

private async Task GetDataAsync() 
{ 
    var result = await MyWebService.GetDataAsync(); 
    return result.ToString(); 
} 

Then call it in the ASP.NET MVC controller method as follows:

public ActionResult ActionAsync() 
{ 
    var data = GetDataAsync().Result;      
    return View(data); 
}

Here you will get a deadlock. Why? Because the getter of the property Resultwill block the thread until the operation asyncis completed, but since the method asyncwill automatically return the result to the original thread, and ASP.NET uses a single-threaded synchronization context, they will continue to wait for each other. A similar problem can also occur with WPF, Silverlight, or with C # / XAML Windows Store applications. You can learn more about this here .

Recommendations for using the framework


AV2201 Use C # type aliases instead of types from the namespace System

For example, use objectinstead Object, stringinstead, Stringand intinstead Int32. These aliases were introduced to make primitive types first-class members of the C # language, so use them properly.

Exception : When referring to static elements of such types, it is usually customary to use the full CLS name, for example, Int32.Parse()instead int.Parse().

AV2205 Carefully set the names of properties, variables, or fields that refer to localized resources. The

recommendations in this section apply to localized resources, such as error messages and menu text.

  • Use pascal notation for resource keys
  • Предпочитайте описательные названия коротким. Старайтесь делать их лаконичными, но не потеряйте читаемость
  • Используйте только буквенно-цифровые знаки при именовании ресурсов

AV2207 Do not leave in the code lines that should be changed during application deployment, for

example, connection strings, server addresses, etc. Use resource files, a ConnectionStringsclass property , ConfigurationManageror a class Settingsgenerated by Visual Studio. Maintain the current settings through app.configor web.config(and not elsewhere).

AV2210 Build with the highest level of warnings

Configure your work environment to use warning level 4 for the C # compiler and enable the “Treat warnings as errors” option. This will allow you to compile the code with the highest level of quality possible.

AV2215Carefully fill out the attributes in the file AssemblyInfo.cs

Make sure the attributes for the company name, description, copyright notice, etc. filled out. One way to ensure that the version number and other fields common to all assemblies is always the same, is to move the corresponding attributes from AssemblyInfo.csto a file SolutionInfo.csthat is shared by all projects within the solution.

AV2220 Avoid using LINQ for simple expressions

Instead:

var query = from item in items where item.Length > 0; 

It is better to use a method from the namespace System.Linq:

var query = items.Where(i => i.Length > 0);

In addition, LINQ queries must be split across multiple lines for readability. Thus, the second expression looks more readable.

AV2221 Use lambda expressions instead of anonymous methods

Lambda expressions serve as a more beautiful alternative to anonymous methods. Thus, instead of:

Customer c = Array.Find(customers, delegate(Customer c)  
{  
    return c.Name == “Tom”;  
});

use lambda expression:

Customer c = Array.Find(customers, c => c.Name == “Tom”);

or even better is:

var customer = customers.Where(c => c.Name == “Tom”);

AV2230 Use the keyword dynamiconly when working with objects of this type.

The keyword has dynamicbeen entered for working with dynamic languages. Their use creates a serious performance bottleneck, since the compiler is forced to generate a certain amount of additional code.

Use dynamiconly when referring to members of a dynamically created instance of a class (when using a class Activator) as an alternative Type.GetProperty()and Type.GetMethod()or when working with COM Iterop types.

AV2235 Try to use async/awaitinsteadTask
Using the new C # 5.0 keywords makes writing and reading code easier, which in turn makes it easier to maintain. Even if you need to create numerous asynchronous operations. For example, instead of doing this:

public Task GetDataAsync() 
{ 
    return MyWebService.FetchDataAsync() 
        .ContinueWith(t => new Data (t.Result)); 
}

declare the method as follows:

public async Task GetDataAsync() 
{ 
    var result = await MyWebService.FetchDataAsync();  
    return new Data (result); 
}

Documentation Guidelines


AV2301 Write comments and documentation in American English

AV2305 Document everything public, protectedand internaltypes and members

Documenting your code will allow Visual Studio to display hints when your class will be used elsewhere. In addition, when you document your classes well, you can generate documentation for your code that looks professional.

AV2306 Remember another developer when writing XML documentation. Remember another developer

when writing XML documentation. Maybe he / she will not have access to the source code and you need to try to more fully explain how you can use your type.

AV2307Use the MSDN documentation

style. Follow the MSDN online help style to help another developer understand your documentation more quickly.

Hint : GhostDoc can generate xml comments for creating documentation using hotkeys.

AV2310 Avoid inline comments

If you feel the need to explain a particular section of code using a comment, then most likely you need to put this code in a separate method and give it a name that will clearly indicate its purpose.

AV2316 Write comments only to explain complex solutions and algorithms

Try to have your comments answer questions about why and what , not how . Avoid explaining the code block with words; instead, help someone who reads your code understand why you chose this solution or algorithm and what you are trying to achieve. If choosing an alternative solution, if possible, also explain why a simple solution led to problems.

AV2318 Do not use comments to track work to be done later.

Adding ToDo or some other comment to the code block to keep track of the work that needs to be done may seem like a good solution. But in fact, nobody needs such comments. Use a task tracking system such as Team Foundation Server to track flaws.

Design Recommendations


AV2400 Use General Layout Rules

  • Keep line lengths within 130 characters
  • Use 4 spaces as indents and do not use tabs
  • Insert a single space between expressions (for example if) as well as keywords. Do not use spaces after (and before ). For instance:

    if (condition == null)
    
  • Add a space before and after operators (such as +, -, ==)
  • Всегда используйте конструкции if, else, do, while, for и foreach с парными фигурными скобками, даже если без этого можно обойтись
  • Открывайте и закрывайте парные скобки всегда в новой строке
  • Не устанавливайте значение каждого свойства объекта в новой строке после инициализации этого объекта. Используйте следующий формат:

    var dto = new ConsumerDto() 
    { 
        Id = 123, 
        Name = “Microsoft”, 
        PartnerShip = PartnerShip.Gold, 
    };
    
  • Не разделяйте объявление лямбда-выражения на несколько строк. Используйте формат, как показано ниже:

    methodThatTakesAnAction.Do(x => 
    { 
        // какие-то действия  
    };
    
  • Объявляйте LINQ-запрос в одной строке или объявляйте каждое ключевое слово этого запроса в новой строке, как показано ниже:

    var query = from product in products where product.Price > 10 select product; 
    

    или

    var query =  from product in products 
                 where product.Price > 10 
                 select product;
    
  • Начинайте LINQ-запрос с объявления всех выражений from и не перемешивайте их выражениями where
  • Добавляйте скобки вокруг каждого сравнения в условном выражении, но не добавляйте их вокруг одиночного выражения. Например:

    if (!string.IsNullOrEmpty(str) && (str != “new”))
    
  • Add a blank line between multiline expressions, class members, after closing parentheses, after unrelated code blocks, before and after the keyword, #regionand between directives usingif the namespaces belong to different companies

AV2402 Arrange and group namespaces according to company name

// Пространства имен Microsoft первые 
using System; 
using System.Collections; 
using System.XML; 
// Другие пространства имен идут в алфавитном порядке 
using AvivaSolutions.Business; 
using AvivaSolutions.Standard; 
using Telerik.WebControls; 
using Telerik.Ajax;

AV2406 Arrange class members in a strictly defined order.

  1. Private fields and constants (c #region)
  2. Public constants
  3. Public read-only static fields
  4. Factory methods
  5. Constructors and Finalizers
  6. Events
  7. Public Properties
  8. Other methods and private properties in the order they are called

Maintaining a general order makes it easier for other team members to navigate your code. In general, reading the source file should go from top to bottom, as if you were reading a book. This prevents the situation when you have to browse the file up and down in search of the desired fragment.

AV2407 Use caution with the keyword. #region

The keyword #regionmay be useful, but it may also obscure the main purpose of the class. Therefore, use #regiononly for:

  • Private fields and constants (preferably in the Private Definitions region)
  • Nested classes
  • Interface implementations (only if interface implementation is not the main purpose of this class)


Important Resources


The site of the company


This document is part of an effort to ensure that the daily work of C # developers is professional. Therefore, I post these recommendations on the CodePlex website. You can easily find them at www.csharpcodingguidelines.com .

In addition to the newest version of this document, you will find there:

  • List of short links to this manual
  • Ruleset for Visual Studio 2010/2012 for various types of systems
  • A set of rules for ReSharper that follow the recommendations in Chapter 10
  • A platform for discussing the quality of writing code in C #


useful links


In addition to the many links cited in this document, I would like to recommend the following books, articles, and sites to anyone interested in software quality.

Code Complete: A Practical Handbook of Software Construction (Steve McConnel) . This is one of the best books I've ever read. She tells in detail about all aspects of software development. Even though the original of this book was written in 2004, you will be surprised when you see how relevant the things described in it are. If you want to know the gravity of the words above, I wrote a review of this book in 2009.

The Art of Agile Development (James Shore). Another great all-encompassing journey through many practices taught by methodologies such as Scrum and extreme programming. If you want to quickly become familiar with these methodologies, be sure to read James's book.

Applying Domain Driven-Design and Patterns: With Examples in C # and .NET (Jimmy Nilsson) . The book that started my interest in subject-oriented design (DDD) and development through testing (TDD). This is one of those books that I should have read a few years earlier to avoid many of my mistakes.

Jeremy D. Miller's Blog. Although he no longer runs this blog, in the last couple of years he has written excellent articles on development through testing, patterns, and design principles. I learned a lot from his ideas and examples taken from real life.

LINQ Framework Design Guidelines . A set of rules and guidelines that you should follow when creating your own implementation of the IQueryable interface.

Best Practices for c # async / await . Source and rationale for several of the new recommendations described in this document. The author is John Wagner .

Also popular now: