There are occasionally circumstances in which we need to know data about C# objects, rather than just the object's data. C# provides us with two techniques that allow us to do this: attributes which store the data about elements, and reflection which allows us to access information about elements.
Sample Project
Attributes
Attributes in C# provide a way to associate metadata to C# elements. We specify that we are using an attribute by placing it above the declaration for the element (e.g. the class, property, method, etc. definition) and using braces []
.
The .NET Framework provides several attributes for us. In fact, we have already seen one usage of them in the Structs and Enums post: the [Flags]
attribute.
[Flags] //Attribute
public enum DayOfWeek
{
Sunday = 1,
Monday = 2,
Tuesday = 4,
Wednesday = 8,
Thursday = 16,
Friday = 32,
Saturday = 64
}
There are also several attributes defined by the .NET Framework we can use, such as the [Serializable]
attribute, which tells the C# compiler that a given class can be serialized to another format such as JSON or XML:
[Serializable]
public class SerializableClass { /*...*/ }
It is important to note that Attributes do not provide functionality to the decorated elements; rather, they are "baked in" to the assembly at compile time. In order to access Attributes and the data they contain, we use a technique called Reflection, which will be discussed later.
Custom Attributes
C# allows us to write our own custom attributes. Such an object must inherit from the System.Attribute
abstract class.
Let's imagine we are building a class to represent individual novels published throughout human history. We might want an Attribute to represent information about the novels' authors, including their name and the year the novel was published.
public class AuthorInformationAttribute : Attribute
{
public int YearPublished { get; set; }
public string AuthorName { get; set; }
public AuthorInformationAttribute(int year)
{
YearPublished = year;
}
public AuthorInformationAttribute(string name, int year)
{
AuthorName = name;
YearPublished = year;
}
}
Attributes are a class; they can have properties, methods, constructors, and other members just like any other class.
Decorating Elements
To use our custom attribute, we would decorate another C# element with it. The other element can be a class, a method, a field, a property, an entire assembly, or other things; we will decorate another class:
[AuthorInformation("Miguel de Cervantes", 1605)]
public class DonQuixote { /*...*/ }
Note that since our custom attribute has multiple constructors, we can use either one:
[AuthorInformation(1706)] //Rough date of first English-language publication
public class OneThousandAndOneNights { /*...*/ }
Also, note that even though our class is named AuthorInformationAttribute
, we only need the short name AuthorInformation
in the decoration.
Attribute Usage
We can restrict the kinds of elements that our custom Attributes can decorate using the [AttributeUsage]
attribute:
[AttributeUsage(AttributeTargets.Class
| AttributeTargets.Interface)]
public class AuthorInformationAttribute : Attribute { /*...*/ }
There are many options for the AttributeTargets
flag, including Class
, Interface
, Method
, Constructor
, Enum
, Assembly
and more.
AttributeUsage
also allows us to define whether objects that inherit from the decorated object also get the attribute...
[AttributeUsage(AttributeTargets.Class
| AttributeTargets.Interface, Inherited = true)]
public class AuthorInformationAttribute : Attribute { /*...*/ }
...and whether or not there can be multiple instances of this attribute on a single element:
[AttributeUsage(AttributeTargets.Class
| AttributeTargets.Interface, AllowMultiple = false)]
public class AuthorInformationAttribute : Attribute { /*...*/ }
But we are still left with a question: how do we access the values defined in our custom Attribute?
Retrieving Attributes
We can retrieve information stored in attributes using a special method defined by the .NET Framework and the Attribute
class: GetCustomAttribute()
var bookType = typeof(DonQuixote); //Type of the class
var attributeType = typeof(AuthorInformationAttribute); //Type of the attribute
var attribute =
(AuthorInformationAttribute)Attribute.GetCustomAttribute(bookType,
attributeType);
Console.WriteLine("Published by " + attribute.AuthorName
+ " in " + attribute.YearPublished);
Note that in the first line, the type we need is the type of the decorated element, whether it is a class, a method, an interface, etc.
Reflection
Reflection is a technique that allows us to gather data about an object, rather than the data within the object itself. This information might include an object's type, information about an object's members (including methods, properties, constructors, etc.), and information about a particular assembly. It also includes any information stored in an Attribute on the element.
The simplest form of Reflection, and one that we have already seen early in this series, is the GetType()
method:
int myInt = 5;
Type type = myInt.GetType();
Console.WriteLine(type);
However, there are more options we can use. For example, we can also use Reflection to get information about the assembly that contains a given type:
Assembly assembly = typeof(DateTime).Assembly;
Console.WriteLine(assembly);
Assembly bookAssembly = typeof(OneThousandAndOneNights).Assembly;
Console.WriteLine(bookAssembly);
The code from earlier that uses Attribute.GetCustomAttribute()
is also an example of Reflection.
Reflection is a large topic, and we won't discuss all of the things we can do with it here. One of the ways we can use reflection is to get information about a method in a class:
public class ReflectedClass
{
public string Property1 { get; set; }
public int Add(int first, int second) //This method
{
return first + second;
}
}
ReflectedClass reflected = new ReflectedClass();
MemberInfo member = reflected.GetType().GetMethod("Add"); //Pass name of method
Console.WriteLine(member); //Output: Int32 Add(Int32, Int32)
We can also get information about the defined property, as well as information about the object's constructor, which will be the implicit public parameterless constructor since we did not define an explicit one.
PropertyInfo property = reflected.GetType().GetProperty("Property1");
Console.WriteLine(property); //Output: System.String Property1
ConstructorInfo constructor = reflected.GetType().GetConstructor(new Type[0]);
Console.WriteLine(constructor); //Output: Void .ctor()
Creating an Instance using Reflection
There is a very powerful class in the .NET Framework called Activator
which can create instances of objects from types. Let's use that class to create a new instance of our ReflectedClass
from earlier.
using System; //Activator is in this namespace
ReflectedClass newReflected = new ReflectedClass();
var reflectedType = newReflected.GetType();
object newObject = Activator.CreateInstance(reflectedType);
Console.WriteLine(newObject); //_17AttributesAndReflection.ReflectedClass
Reflection and Generics
Working with Reflection and generic types is a bit trickier than normal types. There is a property on the Type
class to determine if the type is generic:
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7 };
Console.WriteLine(numbers.GetType().IsGenericType); //True
We can also do more complex things, like create a new instance of a generic List<T>
using Reflection:
List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7 };
//Get the generic type definition from this object.
Type d = numbers.GetType().GetGenericTypeDefinition();
//Create an array of type arguments for the generic parameters
//e.g. whatever T is
Type[] typeArgs = new Type[] { typeof(int) }; //T is int
//Make the generic type
Type constructed = d.MakeGenericType(typeArgs);
//Instantiate an object of the constructed generic type
object list = Activator.CreateInstance(constructed);
Console.WriteLine(list.GetType());
//Output: System.Collections.Generic.List`1[System.Int32]
Glossary
- Metadata - Data about data; in C#, means information that will be compiled into the assembly using Attributes.
- Decorate - The term we use for adding an attribute to a C# element; we say we "decorated" the element with the attribute. (Also see Decorator pattern).
Summary
Attributes assign metadata to C# elements, including classes, properties, methods, and more. This metadata is compiled away when the project is built, and describes the element, not the element's data.
We can create custom attributes that inherit from the Attribute
class. We can restrict where those attributes are used with the AttributeUsage
attribute, and we can retrieve attribute data using reflection.
Reflection is a technique that allows us to retrieve metadata and information about an element, rather than the element itself. The most basic way to do reflection is to use the GetType()
method, but we can also use reflection to get information about methods, constructors, properties, and more.
We can even use reflection to create instances of objects, so long as we already have the type. Finally, using reflection to create generic objects is possible, but more tricky; we need the type for the generic object as well as the types for all the generic parameters.
In the next part of this series, we discuss some of the most high-level concepts C# offers: lambdas and expressions. You've already used these, you just don't know it yet.
Happy Coding!