We program in C # 8.0. Attributes

imageHello, Habr! We would like to draw your attention to one new product (handed over to the printing house), which is already available for purchase in electronic form .



The C # language has been around for about two decades. It has evolved steadily in both features and size, but Microsoft has always kept the core specs unchanged. Each new feature should fit perfectly with the previous ones, improving the language, rather than turning it into a disjointed set of different functions.



While C # is still a fairly simple language, there is much more to be said about it than about its first incarnation. Since the book's coverage is large enough, a certain level of technical background is expected from readers.



I offer you an excerpt from the book.



Attributes



In .NET, you can annotate components, types, and their members using attributes. The purpose of an attribute is to regulate or change the behavior of the platform, tool, compiler, or CLR. For example, in Chapter 1, I demonstrated a class annotated with the [TestClass] attribute. He told the unit test framework that a class contains a series of tests that must be executed as part of a test suite.



Attributes only contain information, but do nothing by themselves. Let me draw an analogy with the physical world. If you print a label containing destination and tracking information and affix it to a package, that label alone will not force the package to reach its destination. Such a label is only useful when the cargo is being processed by a transport company. When the company picks up the package, the label will be needed to determine how and where to route the shipment. Thus, the label is important, but its sole purpose is to provide the information that any system requires. It's the same with .NET attributes - they only work if someone expects to find them. Some attributes are handled by the CLR or the compiler, but these are in the minority. Most are used by platforms, libraries,tools (such as unit test systems) or your own code.



Applying Attributes



To avoid having to introduce additional concepts into the type system, .NET treats them as instances of .NET types. To be used as an attribute, a type must derive from the System.Attribute class, and this is its only feature. To apply an attribute, you put the type name in square brackets and usually place it immediately before the target of the attribute. Listing 14.1 shows some of the attributes from the Microsoft testing framework. One I applied to a class to indicate that it contains tests that I would like to run. In addition, I applied attributes to individual methods, telling the test environment which ones are tests and which ones contain the initialization code to run before each test.



Listing 14.1. Attributes in the unit test class



using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ImageManagement.Tests
{
   [TestClass]
   public class WhenPropertiesRetrieved
   {
      private ImageMetadataReader _reader;
      [TestInitialize]
      public void Initialize()
      {
         _reader = new ImageMetadataReader(TestFiles.GetImage());
      }
      [TestMethod]
      public void ReportsCameraMaker()
      {
         Assert.AreEqual(_reader.CameraManufacturer, "Fabrikam");
      }
      [TestMethod]
      public void ReportsCameraModel()
      {
          Assert.AreEqual(_reader.CameraModel, "Fabrikam F450D");
      }
   }
}
      
      





If you look in the documentation for most of the attributes, you will find that their actual names end in Attribute. If there is no class with the parenthesized name, the C # compiler will try to add Attribute, so the [TestClass] attribute in Listing 14.1 refers to the TestClassAttribute class. You can write the complete class name if you like, for example [TestClassAttribute], but more often the shorter form is used.



If you need to apply multiple attributes, then you have two options. You can provide multiple sets of parentheses, or you can put multiple attributes in a single pair of parentheses, separated by commas.



Several attribute types are capable of accepting constructor arguments. For example, the Microsoft testing framework contains the TestCategoryAttribute attribute. At startup, you can choose to run only those tests that belong to a specific category. This attribute requires that you pass the category name as a constructor argument, because it makes no sense to apply this attribute without specifying a name. The syntax for specifying arguments to an attribute constructor is quite expected (Listing 14.2).



Listing 14.2. Attribute with constructor argument



[TestCategory("Property Handling")]
[TestMethod]
public void ReportsCameraMaker()
{
...
      
      





You can also specify properties or field values. The characteristics of some attributes can only be controlled through properties or fields, not through constructor arguments. (If an attribute has many optional settings, it is usually easier to think of them as properties or fields instead of defining a constructor overload for every possible combination of settings.) The syntax is one or more PropertyOrFieldName = Value entries after the constructor arguments (or instead if they are not present) ... Listing 14.3 shows another attribute used in unit testing, ExpectedExceptionAttribute, to indicate that when you run a test, you expect it to throw a specific exception. The type of exception is required, so we pass it as a constructor argument, but this attribute also allows you to specify,whether the test runner should accept exceptions of type derived from the specified one. (By default, it only accepts an exact match.) This behavior is controlled by the AllowDerivedTypes property.



Listing 14.3. Specifying Optional Attribute Settings with Properties



[ExpectedException(typeof(ArgumentException), AllowDerivedTypes = true)]
[TestMethod]
public void ThrowsWhenNameMalformed()
{
...
      
      





Applying an attribute does not create it. All you do when applying an attribute is to provide instructions on how the attribute should be created and initialized if someone needs it. (It is a common misconception that method attributes are created when a method is run. This is not the case.) When the compiler generates metadata for an assembly, it includes information about which attributes were applied to which elements, including the constructor argument list and property values, and the CLR will extract and use this information only if someone needs it. For example, when you tell Visual Studio to run your unit tests, it will load your test assembly and then, for each open type, asks the CLR for any test-related attributes. This is the point at which attributes are created. If you just load the assembly,let's say adding a link to it from another project, and then using some of the types it contains, the attributes will not be created - they will remain nothing more than a set of instructions lost in the metadata of your assembly.



Attribute Targets



Attributes can be applied to many different types of targets. You can place attributes in any of the type system functions introduced in the reflection API in Chapter 13. In particular, you can apply attributes to assemblies, modules, types, methods, method parameters, constructors, fields, properties, events, and generic type parameters. In addition, you can provide attributes that target the return value of a method.



In most cases, you designate a target by simply placing an attribute directly in front of it. But that won't work for assemblies or modules, because there is nothing in your source code to represent them β€” everything in your project goes into the assembly it produces. Modules, in turn, are also a collection (usually making up an assembly, as I described in Chapter 12). Therefore, for them, we must explicitly specify the target at the beginning of the attribute. You will often see assembly-level attributes like those shown in Listing 14.4 in the GlobalSuppressions.cs file. Visual Studio sometimes offers options for modifying your code, and if you choose to suppress this functionality, you can do so using assembly-level attributes.



Listing 14.4. Assembly level attributes



[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage(
   "StyleCop.CSharp.NamingRules",
   "SA1313:Parameter names should begin with lower-case letter",
   Justification = "Triple underscore acceptable for unused lambda parameter",
   Scope = "member",
   Target = "~M:Idg.Examples.SomeMethod")]
      
      





Module-level attributes follow the same pattern, although they are much less common. This is not least because multi-module assemblies are rare and not supported by .NET Core. Listing 14.5 shows how to set up a specific module to debug if you want one module in a multi-module build to be easy to debug and the rest to be JIT compiled with full optimization. (This is a specially made script that I can use to show you the syntax. In practice, you probably wouldn't want to do this.) I'll cover the DebuggableAttribute later in the JIT compilation section on p. 743.



Listing 14.5. Module level attribute



using System.Diagnostics;
[module: Debuggable(
DebuggableAttribute.DebuggingModes.DisableOptimizations)]
      
      





The return values ​​of methods can be annotated, and this also requires skill, because the return value attributes are placed before the method, in the same place as the attributes that apply to the method itself. (Attributes for parameters do not need to be qualified because they appear in parentheses along with the arguments.) Listing 14.6 shows a method with attributes that apply to both the method and the return type. (The attributes in this example are part of interop services that allow .NET code to call external code, such as an OS API. This example imports a Win32 library function so that it can be used from C #. There are several different representations for booleans in unmanaged code.so in this case, I've annotated the return type with the MarshalAsAttribute, specifying which type the CLR should expect.)



You can pre-order a paper book on our website



All Articles