Skip to content
wannacode

Reflection and attributes

In short: Reflection is a program looking at its own types while it runs: properties and methods by name, and attributes are notes it can read.

Problem

The property that ToString forgot

The order's ToString is written by hand. Later, someone adds a property for the discount:

class Order
{
public int Number { get; set; } = 1042;
public string Customer { get; set; } = "Maria Petrova";
public double Total { get; set; } = 86.40;
public int Discount { get; set; } = 10; // added later
public override string ToString()
{
return "Order[Number=" + Number + ", Customer=" + Customer + ", Total=" + Total + "]";
}
}
Console.WriteLine(new Order());
Output
Order[Number=1042, Customer=Maria Petrova, Total=86.4]

The discount isn't in the output, because nobody remembered to update ToString. With ten classes, this happens all the time.

There's another question from the start of the module, too. When you write [Fact] above a test method (in NUnit it's [Test]), xUnit finds it and runs it, even though it has never seen your class. How? Both things work through the same idea: a program can look at its own classes while it runs.

The idea

The class as an object

For every type, .NET keeps an object of type Type that describes it: name, parent, interfaces, properties, fields, methods, constructors. Getting to it is called reflection (the program sees itself as in a mirror). The classes for the parts of a type (PropertyInfo, MethodInfo and others) are in System.Reflection. There are three ways to get the Type object:

  • typeof(Order), when you know the class while you write the code;
  • obj.GetType(), when you have an object (it gives the object's real class, not the type of the variable);
  • Type.GetType("System.Collections.ArrayList"), when you only have the name as text.
Type type = Type.GetType("System.Collections.ArrayList");
Console.WriteLine("Name: " + type.FullName);
Console.WriteLine("Short name: " + type.Name);
Console.WriteLine("Parent: " + type.BaseType.FullName);
foreach (Type parent in type.GetInterfaces().OrderBy(t => t.Name))
{
Console.WriteLine("Interface: " + parent.FullName);
}
Output
Name: System.Collections.ArrayList
Short name: ArrayList
Parent: System.Object
Interface: System.ICloneable
Interface: System.Collections.ICollection
Interface: System.Collections.IEnumerable
Interface: System.Collections.IList
ArrayList is the old list with no element type, from before List<T>. If there's no type with that name, Type.GetType doesn't throw: it returns null. The interfaces are sorted by name with OrderBy, because the order of GetInterfaces() isn't guaranteed. The name of a generic class ends with ` and the number of its type parameters: typeof(List<string>).Name is List`1.

See it

The properties of any object

GetProperties() gives the public properties of the class as PropertyInfo objects (from System.Reflection). Each one knows its name and type and can read its value from a given object:
static void Dump(object obj)
{
Type type = obj.GetType();
Console.WriteLine(type.Name + ":");
foreach (PropertyInfo property in type.GetProperties())
{
Console.WriteLine(" " + property.Name + " (" + property.PropertyType.Name + ") = " + property.GetValue(obj));
}
}
class Room
{
public int Number { get; set; } = 214;
public bool SeaView { get; set; } = true;
}
Dump(new Order());
Dump(new Room());
Output
Order:
Number (Int32) = 1042
Customer (String) = Maria Petrova
Total (Double) = 86.4
Discount (Int32) = 10
Room:
Number (Int32) = 214
SeaView (Boolean) = True
One method prints any object, and the new Discount property is already there. In C#, the data of a class is seen from outside as properties, and the fields stay hidden. That's why reflection here, and libraries like System.Text.Json and Entity Framework too, look at the properties. They're public, so nothing needs to be opened up, as with setAccessible in Java. The types have their .NET names: int is Int32, double is Double, bool is Boolean.

static void Dump(object obj)
{
Type type = obj.GetType();
Console.WriteLine(type.Name + ":");
foreach (PropertyInfo property in type.GetProperties())
{
Console.WriteLine(" " + property.Name + " (" + property.PropertyType.Name + ") = " + property.GetValue(obj));
}
}
Step by step, in words
  1. Dump gets an Order object. Its fields are hidden; from outside you see its properties.
  2. GetType() gives the Type object that describes the Order class.
  3. The first line is the short name of the class.
  4. GetProperties() gives 4 PropertyInfo objects: each one knows the property's name and type.
  5. GetValue(obj) calls the get of the Number property and returns the value: 1042. Int32 is the .NET name of int.
  6. Customer (String) = Maria Petrova.
  7. Total (Double) = 86.4.
  8. Discount: the property that ToString forgot shows up by itself, because reflection gets all the properties.
  9. The method knows nothing about Order. For Room it prints Number and SeaView the same way.
More precisely, if you are curious
  • GetProperties() gives only the public properties, the inherited ones too. You ask for other things with BindingFlags: BindingFlags.Instance | BindingFlags.NonPublic gives the private ones, and BindingFlags.DeclaredOnly only those of the class itself, without the inherited ones.
  • Behind every auto-property ({ get; set; }), the compiler puts a hidden private field. Here are the fields of Room, asked for with GetFields:
Room room = new Room();
foreach (FieldInfo field in typeof(Room).GetFields(BindingFlags.Instance | BindingFlags.NonPublic))
{
Console.WriteLine(field.Name + " = " + field.GetValue(room));
}
Output
<Number>k__BackingField = 214
<SeaView>k__BackingField = True
  • .NET doesn't promise the order of the properties. Today's .NET returns them in the order of the class, but code that relies on the order is fragile.

Try it

Whose class?

What will the program print?

class Document
{
}
class Invoice : Document
{
}
Document doc = new Invoice();
Console.WriteLine(doc.GetType().Name);
Console.WriteLine(doc.GetType().BaseType.Name);
Console.WriteLine(typeof(Document).BaseType.Name);
Answer

See it

Object, method and private field by name

Through reflection you can create an object, call a method and change a field, knowing only their names:
class Product
{
private string name;
private double price;
public Product(string name, double price)
{
this.name = name;
this.price = price;
}
public void ApplyDiscount(int percent)
{
price = price * (100 - percent) / 100;
}
private string Label()
{
return name + ": €" + price;
}
}
Type type = typeof(Product);
Product coffee = (Product)Activator.CreateInstance(type, "Coffee", 10.0);
MethodInfo discount = type.GetMethod("ApplyDiscount");
discount.Invoke(coffee, new object[] { 20 });
FieldInfo price = type.GetField("price", BindingFlags.Instance | BindingFlags.NonPublic);
Console.WriteLine("Price after discount: " + price.GetValue(coffee));
price.SetValue(coffee, 9.5);
MethodInfo label = type.GetMethod("Label", BindingFlags.Instance | BindingFlags.NonPublic);
Console.WriteLine(label.Invoke(coffee, null));
Output
Price after discount: 8
Coffee: €9.5
  • Activator.CreateInstance(type, "Coffee", 10.0) finds the constructor by the arguments and calls it. It returns an object, which is why there's the cast to Product. You can also find a constructor by the types of its parameters with type.GetConstructor(new[] { typeof(string), typeof(double) }).
  • GetMethod("ApplyDiscount") finds the public method, and Invoke(object, arguments) calls it on coffee. The arguments are an object[] array; with no arguments you pass null.
  • You look for private things with BindingFlags.Instance | BindingFlags.NonPublic. Without them, GetField("price") returns null. You don't need anything like setAccessible: once found, the private field can be read and written, and the private method can be called.

C# has no checked exceptions, so Main declares nothing. If the name is wrong, GetField and GetMethod don't throw: they return null.

More precisely, if you are curious
A List<string>, created and filled without new List or .Add appearing in the code:
Type type = typeof(List<>).MakeGenericType(typeof(string));
object list = Activator.CreateInstance(type);
MethodInfo add = type.GetMethod("Add");
add.Invoke(list, new object[] { "Coffee" });
add.Invoke(list, new object[] { "Tea" });
Console.WriteLine(type);
Console.WriteLine(string.Join(", ", (List<string>)list));
Output
System.Collections.Generic.List`1[System.String]
Coffee, Tea

typeof(List<>) is the generic class without its type parameter, and MakeGenericType makes a List<string> out of it. Unlike Java, in .NET the element type is still there while the program runs: Add on this list takes only a string. If you pass a number to Invoke, it throws:

Output
ArgumentException: Object of type 'System.Int32' cannot be converted to type 'System.String'.

Try it

The price that isn't there

The program compiles, but it stops with a NullReferenceException on the third line. Which line has the bug?

Pick the line with the bug.

The idea

Your own attributes

Attributes are labels in square brackets: [Obsolete], [Fact], [HttpGet]. In Java the same thing is called an annotation and is written with @. On their own, they do nothing. Code that reads them gives them meaning, often through reflection. You can make your own, too: an attribute is a class that inherits from Attribute. This one puts a label on a property, and the method prints only the properties with a label:
[AttributeUsage(AttributeTargets.Property)]
class LabelAttribute : Attribute
{
public string Text { get; }
public LabelAttribute(string text)
{
Text = text;
}
}
class Booking
{
[Label("Number")]
public int Number { get; set; } = 501;
[Label("Guest")]
public string Guest { get; set; } = "Ivan Georgiev";
[Label("Nights")]
public int Nights { get; set; } = 3;
public string InternalNote { get; set; } = "VIP";
}
static void PrintLabeled(object obj)
{
foreach (PropertyInfo property in obj.GetType().GetProperties())
{
LabelAttribute label = property.GetCustomAttribute<LabelAttribute>();
if (label != null)
{
Console.WriteLine(label.Text + ": " + property.GetValue(obj));
}
}
}
Output
Number: 501
Guest: Ivan Georgiev
Nights: 3
  • class LabelAttribute : Attribute declares an attribute. By convention the name ends in Attribute, and when you use it you write it without that: [Label("Guest")]. The argument in the brackets goes to the constructor, and Text is a read-only property.
  • [AttributeUsage(AttributeTargets.Property)] says where it can go: here, only on properties. [Label] above a method is a compile error (CS0592).
  • There's nothing like Java's @Retention here: attributes always stay in the compiled program, and reflection sees them.
  • property.GetCustomAttribute<LabelAttribute>() returns the attribute, or null if the property doesn't have it. InternalNote has no label and isn't printed.
More precisely, if you are curious
  • An attribute can also have properties with set. You set them by name after the arguments: [Label("Guest", Hint = "first and last name")], if LabelAttribute has a Hint property. A property you don't set keeps its value from the class. In Java, the same is @Label(value = "Guest", hint = "…"), with default for the default value.
  • AttributeTargets also has Method, Class, Field, Parameter and others, and you can combine several with |. With AllowMultiple = true, one attribute can appear several times in the same place.
  • IsDefined(typeof(LabelAttribute)) only checks whether the attribute is there, without creating it. You'll use it in the next card.

See it

How xUnit finds the tests

Here's a tiny xUnit. The [Check] attribute is for methods, and the test class checks the discount from the first lesson of the module (with the old bug price > 100):
[AttributeUsage(AttributeTargets.Method)]
class CheckAttribute : Attribute
{
}
class DiscountChecks
{
[Check]
public void TenPercentOff()
{
Expect(45.0, Discount.Apply(50, 10));
}
[Check]
public void ExtraFivePercentFrom100()
{
Expect(85.0, Discount.Apply(100, 10));
}
void Expect(double expected, double actual)
{
if (expected != actual)
{
throw new Exception("expected " + expected + ", but was " + actual);
}
}
}
static void RunChecks(Type type)
{
int passed = 0;
int failed = 0;
foreach (MethodInfo method in type.GetMethods().OrderBy(m => m.Name))
{
if (!method.IsDefined(typeof(CheckAttribute)))
{
continue;
}
object instance = Activator.CreateInstance(type);
try
{
method.Invoke(instance, null);
Console.WriteLine("PASS " + method.Name);
passed++;
}
catch (TargetInvocationException e)
{
Console.WriteLine("FAIL " + method.Name + ": " + e.InnerException.Message);
failed++;
}
}
Console.WriteLine("Checks: " + (passed + failed) + ", passed: " + passed + ", failed: " + failed);
}
Output
FAIL ExtraFivePercentFrom100: expected 85, but was 90
PASS TenPercentOff
Checks: 2, passed: 1, failed: 1

xUnit does the same, only with many more details:

  1. it gets the public methods of the class with GetMethods() and sorts them with OrderBy, so the order is always the same;
  2. it skips the ones without the attribute: IsDefined checks whether they have it;
  3. it makes a new object for each test with Activator.CreateInstance, so the tests don't get in each other's way;
  4. it calls the method with Invoke. If the test throws, Invoke wraps the exception in a TargetInvocationException, and the real one is in InnerException.

GetMethods() also gives the public methods inherited from object (ToString, Equals and others). They don't have the attribute and are skipped. Assert.Equal in xUnit is also a method that throws an exception when the values differ. Exactly like Expect here.

Try it

The checks that disappeared

The checks are written without public, and everything else is the same:

What will RunChecks print?

class DiscountChecks
{
[Check]
void TenPercentOff() { ... }
[Check]
void ExtraFivePercentFrom100() { ... }
}
RunChecks(typeof(DiscountChecks));
Answer

Exercise

Code that works with any class

Solve the tasks in your editor.

1. All the properties. Write PrintProperties(object obj), which prints every property as name = value, including the inherited ones, with the parent's properties first. GetProperties() gives the inherited ones too, but the subclass's come first. So start from obj.GetType(), go up with BaseType until object, and for each class take only its own properties with BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly.
class Vehicle
{
public string Plate { get; set; } = "CA 1234 BK";
public int Year { get; set; } = 2019;
}
class Truck : Vehicle
{
public double LoadTons { get; set; } = 12.5;
}
Output for new Truck()
Plate = CA 1234 BK
Year = 2019
LoadTons = 12.5
2. Mini JSON. Write ToJson(object obj), which returns the properties as JSON: the names in quotes, text in quotes, numbers and true/false without quotes. Use a List<string> with string.Join, is string and is bool: false.ToString() is False, but JSON needs false. 45.0 in C# prints as 45. (The ready-made version is JsonSerializer.Serialize(obj) from System.Text.Json.)
class Ticket
{
public int Seat { get; set; } = 14;
public string Event { get; set; } = "Concert";
public double Price { get; set; } = 45.0;
public bool Vip { get; set; } = false;
}
Output for new Ticket() and new Room()
{"Seat": 14, "Event": "Concert", "Price": 45, "Vip": false}
{"Number": 214, "SeaView": true}
3. Your own test runner. Make the attributes [MyTest] and [MyBeforeEach] and a method Run(Type type). For each method with [MyTest]: a new object, then the method with [MyBeforeEach] (if there is one), then the test. If the test throws, print the kind (GetType().Name) and the message of the exception. Try it with these tests:
[AttributeUsage(AttributeTargets.Method)]
class MyTestAttribute : Attribute
{
}
[AttributeUsage(AttributeTargets.Method)]
class MyBeforeEachAttribute : Attribute
{
}
class Account
{
public int Balance { get; private set; }
public Account(int balance)
{
Balance = balance;
}
public void Withdraw(int amount)
{
if (amount > Balance)
{
throw new ArgumentException("Short by €" + (amount - Balance));
}
Balance -= amount;
}
}
class AccountChecks
{
private Account account;
[MyBeforeEach]
public void Fresh()
{
account = new Account(100);
}
[MyTest]
public void Withdraw40()
{
account.Withdraw(40);
if (account.Balance != 60) throw new Exception("expected 60, but was " + account.Balance);
}
[MyTest]
public void Withdraw70()
{
account.Withdraw(70);
if (account.Balance != 30) throw new Exception("expected 30, but was " + account.Balance);
}
[MyTest]
public void WithdrawTooMuch()
{
account.Withdraw(130);
}
}
Output
PASS Withdraw40
PASS Withdraw70
FAIL WithdrawTooMuch: ArgumentException: Short by €30
Tests: 3, passed: 2, failed: 1

Test

Check yourself

1. What does obj.GetType() give you?
2. What do you need to find a private field with GetField("price", …)?
3. There's a typo in GetField("prise", …). When will you find out?
4. Why doesn't RunChecks find checks written without public?
5. How does xUnit find the methods with [Fact]?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·