Skip to content

Commit 43e4d80

Browse files
mgrovesBillWagner
authored andcommitted
dotnet#71 - attributes tutorial and sample code (also added attributes link… (dotnet#1462)
* dotnet#71 - attributes tutorial and sample code (also added attributes link to ToC) * dotnet#71 - incorporated feedback, added link to index, and updated source code snippet
1 parent 56f4804 commit 43e4d80

File tree

4 files changed

+331
-0
lines changed

4 files changed

+331
-0
lines changed

docs/csharp/tutorials/attributes.md

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
---
2+
title: Attributes | C#
3+
description: Learn how attributes work in C#
4+
keywords: .NET, .NET Core, C#, attributes
5+
author: mgroves
6+
ms.author: wiwagn
7+
ms.date: 1/22/2017
8+
ms.topic: article
9+
ms.prod: .net
10+
ms.technology: devlang-csharp
11+
ms.devlang: csharp
12+
ms.assetid: b152cf36-76e4-43a5-b805-1a1952e53b79
13+
---
14+
15+
# Using Attributes in C# #
16+
17+
Attributes provide a way of associating information with code in a declarative way. They can also provide a reusable element that can be applied to a variety of targets.
18+
19+
Consider the `[Obsolete]` attribute. It can be applied to classes, structs, methods, constructors, and more. It _declares_ that the element is obsolete. It's then up to the C#
20+
compiler to look for this attribute, and do some action in response.
21+
22+
In this tutorial, you'll be introduced to how to add attributes to your code, how to create and use your own attributes, and how to use some
23+
attributes that are built into .NET Core.
24+
25+
## Prerequisites
26+
You’ll need to setup your machine to run .NET core. You can find the
27+
installation instructions on the [.NET Core](https://www.microsoft.com/net/core)
28+
page.
29+
You can run this application on Windows, Ubuntu Linux, macOS or in a Docker container.
30+
You’ll need to install your favorite code editor. The descriptions below
31+
use [Visual Studio Code](https://code.visualstudio.com/) which is an open
32+
source, cross platform editor. However, you can use whatever tools you are
33+
comfortable with.
34+
35+
## Create the Application
36+
37+
Now that you've installed all the tools, create a new .NET Core
38+
application. To use the command line generator, execute the following command in your favorite shell:
39+
40+
`dotnet new`
41+
42+
This command will create barebones .NET core project files. You will need to execute `dotnet restore` to restore the dependencies needed to compile this project.
43+
44+
To execute the program, use `dotnet run`. You should see "Hello, World" output to the console.
45+
46+
## How to add attributes to code
47+
48+
In C#, attributes are classes that inherit from the `Attribute` base class. Any class that inherits from `Attribute` can be used as a sort of "tag" on other pieces of code.
49+
For instance, there is an attribute called `ObsoleteAttribute`. This is used to signal that code is obsolete and shouldn't be used anymore. You can place this attribute on a class,
50+
for instance, by using square brackets.
51+
52+
[!code-csharp[Obsolete attribute example](../../../samples/snippets/csharp/tutorials/attributes/Program.cs#ObsoleteExample1)]
53+
54+
Note that while the class is called `ObsoleteAttribute`, it's only necessary to use `[Obsolete]` in the code. This is a convention that C# follows.
55+
You can use the full name `[ObsoleteAttribute]` if you choose.
56+
57+
When marking a class obsolete, it's a good idea to provide some information as to *why* it's obsolete, and/or *what* to use instead. Do this by passing a string
58+
parameter to the Obsolete attribute.
59+
60+
[!code-csharp[Obsolete attribute example with parameters](../../../samples/snippets/csharp/tutorials/attributes/Program.cs#ObsoleteExample2)]
61+
62+
The string is being passed as an argument to an `ObsoleteAttribute` constructor, just as if you were writing `var attr = new ObsoleteAttribute("some string")`.
63+
64+
Parameters to an attribute constructor are limited to simple types/literals: `bool, int, double, string, Type, enums, etc` and arrays of those types.
65+
You can not use an expression or a variable. You are free to use positional or named parameters.
66+
67+
## How to create your own attribute
68+
69+
Creating an attribute is as simple as inheriting from the `Attribute` base class.
70+
71+
[!code-csharp[Create your own attribute](../../../samples/snippets/csharp/tutorials/attributes/Program.cs#CreateAttributeExample1)]
72+
73+
With the above, I can now use `[MySpecial]` (or `[MySpecialAttribute]`) as an attribute elsewhere in the code base.
74+
75+
[!code-csharp[Using your own attribute](../../../samples/snippets/csharp/tutorials/attributes/Program.cs#CreateAttributeExample2)]
76+
77+
Attributes in the .NET base class library like `ObsoleteAttribute` trigger certain behaviors within the compiler. However, any attribute you create acts
78+
only as metadata, and doesn't result in any code within the attribute class being executed. It's up to you to act
79+
on that metadata elsewhere in your code (more on that later in the tutorial).
80+
81+
There is a 'gotcha' here to watch out for. As mentioned above, only certain types are allowed to be passed as arguments when using attributes. However, when creating an attribute type,
82+
the C# compiler won't stop you from creating those parameters. In the below example, I've created an attribute with a constructor that compiles just fine.
83+
84+
[!code-csharp[Valid constructor used in an attribute](../../../samples/snippets/csharp/tutorials/attributes/Program.cs#AttributeGothca1)]
85+
86+
However, you will be unable to use this constructor with attribute syntax.
87+
88+
[!code-csharp[Invalid attempt to use the attribute constructor](../../../samples/snippets/csharp/tutorials/attributes/Program.cs#AttributeGotcha2)]
89+
90+
The above will cause a compiler error like `Attribute constructor parameter 'myClass' has type 'Foo', which is not a valid attribute parameter type`
91+
92+
## How to restrict attribute usage
93+
94+
Attributes can be used on a number of "targets". The above examples show them on classes, but they can also be used on:
95+
96+
* Assembly
97+
* Class
98+
* Constructor
99+
* Delegate
100+
* Enum
101+
* Event
102+
* Field
103+
* GenericParameter
104+
* Interface
105+
* Method
106+
* Module
107+
* Parameter
108+
* Property
109+
* ReturnValue
110+
* Struct
111+
112+
When you create an attribute class, by default, C# will allow you to use that attribute on any of the possible attribute targets. If you want to restrict your attribute
113+
to certain targets, you can do so by using the `AttributeUsageAttribute` on your attribute class. That's right, an attribute on an attribute!
114+
115+
[!code-csharp[Using your own attribute](../../../samples/snippets/csharp/tutorials/attributes/Program.cs#AttributeUsageExample1)]
116+
117+
If you attempt to put the above attribute on something that's not a class or a struct, you will get a compiler error
118+
like `Attribute 'MyAttributeForClassAndStructOnly' is not valid on this declaration type. It is only valid on 'class, struct' declarations`
119+
120+
[!code-csharp[Using your own attribute](../../../samples/snippets/csharp/tutorials/attributes/Program.cs#AttributeUsageExample2)]
121+
122+
## How to use attributes attached to a code element
123+
124+
Attributes act as metadata. Without some outward force, they won't actually do anything.
125+
126+
To find and act on attributes, [Reflection](../programming-guide/concepts/reflection.md) is generally needed. I won't cover Reflection in-depth in this tutorial, but the basic
127+
idea is that Reflection allows you to write code in C# that examines other code.
128+
129+
For instance, you can use Reflection to get information about a class:
130+
131+
[!code-csharp[Getting type information with Reflection](../../../samples/snippets/csharp/tutorials/attributes/Program.cs#ReflectionExample1)]
132+
133+
That will print out something like: `The assembly qualified name of MyClass is ConsoleApplication.MyClass, attributes, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null`
134+
135+
Once you have a `TypeInfo` object (or a `MemberInfo`, `FieldInfo`, etc), you can use the `GetCustomAttributes` method. This will return a collection of `Attribute` objects.
136+
You can also use `GetCustomAttribute` and specify an Attribute type.
137+
138+
Here's an example of using `GetCustomAttributes` on a `MemberInfo` instance for `MyClass` (which we saw earlier has an `[Obsolete]` attribute on it).
139+
140+
[!code-csharp[Getting type information with Reflection](../../../samples/snippets/csharp/tutorials/attributes/Program.cs#ReflectionExample2)]
141+
142+
That will print to console: `Attribute on MyClass: ObsoleteAttribute`. Try adding other attributes to `MyClass`.
143+
144+
It's important to note that these `Attribute` objects are instantiated lazily. That is, they won't be instantiated until you use `GetCustomAttribute` or `GetCustomAttributes`.
145+
They are also instantiated each time. Calling `GetCustomAttributes` twice in a row will return two different instances of `ObsoleteAttribute`.
146+
147+
## Common attributes in the base class library (BCL)
148+
149+
Attributes are used by many tools and frameworks. NUnit uses attributes like `[Test]` and `[TestFixture]` that are used by the NUnit test runner. ASP.NET MVC uses attributes like `[Authorize]`
150+
and provides an action filter framework to perform cross-cutting concerns on MVC actions. [PostSharp](https://www.postsharp.net) uses the attribute syntax to allow aspect-oriented programming in C#.
151+
152+
Here are a few notable attributes built into the .NET Core base class libraries:
153+
154+
* `[Obsolete]`. This one was used in the above examples, and it lives in the `System` namespace. It is useful to provide declarative documentation about a changing code base. A message can be provided in the form of a string,
155+
and another boolean parameter can be used to escalate from a compiler warning to a compiler error.
156+
157+
* `[Conditional]`. This attribute is in the `System.Diagnostics` namespace. This attribute can be applied to methods (or attribute classes). You must pass a string to the constructor.
158+
If that string matches a `#define` directive, then any calls to that method (but not the method itself) will be removed by the C# compiler. Typically this is used for debugging (diagnostics) purposes.
159+
160+
* `[CallerMemberName]`. This attribute can be used on parameters, and lives in the `System.Runtime.CompilerServices` namespace. This is an attribute that is used to inject the name
161+
of the method that is calling another method. This is typically used as a way to eliminate 'magic strings' when implementing INotifyPropertyChanged in various UI frameworks. As an
162+
example:
163+
164+
[!code-csharp[Using CallerMemberName when implementing INotifyPropertyChanged](../../../samples/snippets/csharp/tutorials/attributes/Program.cs#ReflectionExample1)]
165+
166+
In the above code, you don't have to have a literal `"Name"` string. This can help prevent typo-related bugs and also makes for smoother refactoring/renaming.
167+
168+
## Summary
169+
170+
Attributes bring declarative power to C#. But they are a form of code as meta-data, and don't act by themselves.

docs/csharp/tutorials/index.md

+2
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,5 @@ features in the C# language.
3030
* [Inheritance](inheritance.md): demonstrates how class and interface inheritance provide code reuse in C#.
3131

3232
* [String Interpolation](string-interpolation.md): demonstrates many of the uses for the `$` string interpolation in C#.
33+
34+
* [Using Attributes](attributes.md): how to create and use attributes in C#.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel;
4+
using System.Diagnostics;
5+
using System.Reflection;
6+
using System.Runtime.CompilerServices;
7+
8+
namespace ConsoleApplication
9+
{
10+
public class Program
11+
{
12+
public static void Main(string[] args)
13+
{
14+
var c = new MyClass(); // this should trigger a warning at compile time since MyClass has an Obsolete attribute
15+
16+
// this code enumerates all the possible targets of an attributes
17+
// for purposes of compile time checking
18+
var targets = new List<AttributeTargets>();
19+
targets.Add(AttributeTargets.All);
20+
targets.Add(AttributeTargets.Assembly);
21+
targets.Add(AttributeTargets.Class);
22+
targets.Add(AttributeTargets.Constructor);
23+
targets.Add(AttributeTargets.Delegate);
24+
targets.Add(AttributeTargets.Enum);
25+
targets.Add(AttributeTargets.Event);
26+
targets.Add(AttributeTargets.Field);
27+
targets.Add(AttributeTargets.GenericParameter);
28+
targets.Add(AttributeTargets.Interface);
29+
targets.Add(AttributeTargets.Method);
30+
targets.Add(AttributeTargets.Module);
31+
targets.Add(AttributeTargets.Parameter);
32+
targets.Add(AttributeTargets.Property);
33+
targets.Add(AttributeTargets.ReturnValue);
34+
targets.Add(AttributeTargets.Struct);
35+
36+
// <ReflectionExample1>
37+
TypeInfo typeInfo = typeof(MyClass).GetTypeInfo();
38+
Console.WriteLine("The assembly qualified name of MyClass is " + typeInfo.AssemblyQualifiedName);
39+
// </ReflectionExample1>
40+
41+
// <ReflectionExample2>
42+
var attrs = typeInfo.GetCustomAttributes();
43+
foreach(var attr in attrs)
44+
Console.WriteLine("Attribute on MyClass: " + attr.GetType().Name);
45+
// </ReflectionExample2>
46+
}
47+
}
48+
49+
// <ObsoleteExample1>
50+
[Obsolete]
51+
public class MyClass
52+
{
53+
54+
}
55+
// </ObsoleteExample1>
56+
57+
// <ObsoleteExample2>
58+
[Obsolete("ThisClass is obsolete. Use ThisClass2 instead.")]
59+
public class ThisClass
60+
{
61+
62+
}
63+
// </ObsoleteExample2>
64+
65+
// <CreateAttributeExample1>
66+
public class MySpecialAttribute : Attribute
67+
{
68+
69+
}
70+
// </CreateAttributeExample1>
71+
72+
// <CreateAttributeExample2>
73+
[MySpecial]
74+
public class SomeOtherClass
75+
{
76+
77+
}
78+
// </CreateAttributeExample2>
79+
80+
// <AttributeUsageExample1>
81+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
82+
public class MyAttributeForClassAndStructOnly : Attribute
83+
{
84+
85+
}
86+
// </AttributeUsageExample1>
87+
88+
// <AttributeUsageExample2>
89+
public class Foo
90+
{
91+
// if the below attribute was uncommented, it would cause a compiler error
92+
// [MyAttributeForClassAndStructOnly]
93+
public Foo() {
94+
95+
}
96+
}
97+
// </AttributeUsageExample2>
98+
99+
// <CallerMemberName1>
100+
public class MyUIClass : INotifyPropertyChanged
101+
{
102+
public event PropertyChangedEventHandler PropertyChanged;
103+
104+
public void ExecutePropertyChanged([CallerMemberName] string propertyName = null)
105+
{
106+
if(PropertyChanged != null)
107+
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
108+
}
109+
110+
private string _name;
111+
public string Name {
112+
get { return _name;}
113+
set {
114+
if(value != _name) {
115+
_name = value;
116+
ExecutePropertyChanged(); // notice that "Name" is not needed here explicitly
117+
}
118+
}
119+
}
120+
}
121+
// </CallerMemberName1>
122+
123+
// <AttributeGothca1>
124+
public class GotchaAttribute : Attribute
125+
{
126+
public GotchaAttribute(Foo myClass, string str) {
127+
128+
}
129+
}
130+
// </AttributeGothca1>
131+
132+
// <AttributeGotcha2>
133+
[Gotcha(new Foo(), "test")] // does not compile
134+
public class AttributeFail
135+
{
136+
137+
}
138+
// </AttributeGotcha2>
139+
140+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "1.0.0-*",
3+
"buildOptions": {
4+
"debugType": "portable",
5+
"emitEntryPoint": true
6+
},
7+
"dependencies": {},
8+
"frameworks": {
9+
"netcoreapp1.1": {
10+
"dependencies": {
11+
"Microsoft.NETCore.App": {
12+
"type": "platform",
13+
"version": "1.1.0"
14+
}
15+
},
16+
"imports": "dnxcore50"
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)