Value-Based Attributes in C#
Tags: csharp, dotnet, attributes
Published: 2020-09-20 00:59:24

---
title: Value-Based Attributes in C#
published: true
description: use constants instead of custom types
tags: csharp, dotnet, attributes
//cover_image: https://direct_url_to_image.jpg
---

# Value-Based Attributes in C Sharp

> This code is a snippet from the https://github.com/ZacharyPatten/Towel project.
> Please check out the project if you want to see more code like it. :)

Attributes in C# are great. They let you add metadata onto members of your code that can be dynamically looked up at runtime. However, they are also a bit of a pain to deal with. You generally have to make your own class that inherits `System.Attribute` and add a decent bit of boiler plate code to look up your attribute with reflection. Here is an example of making your own attribute:
```cs
using System;
using System.Linq;
using System.Reflection;

static class Program
{
	static void Main()
	{
		MyAttribute attribute = typeof(MyClass).GetCustomAttributes<MyAttribute>().FirstOrDefault();
		Console.WriteLine("MyCustomAttribute...");
		if (!(attribute is null))
		{
			Console.WriteLine("Found: " + true);
			Console.WriteLine("Value: " + attribute.Value);
		}
		else
		{
			Console.WriteLine("Found: " + false);
			Console.WriteLine("Value: " + null);
		}
	}
}

public class MyAttribute : Attribute
{
	public string Value { get; private set; }

	public MyAttribute(string value)
	{
		Value = value;
	}
}

[MyAttribute("hello world")]
public class MyClass { }
```

Sure... it's not a ton of boiler plate... but it is still annoying... so I made a value-based attribute so that I can use constant values rather than having to define a new attribute type:
```cs
using System;
using Towel;

static class Program
{
	static void Main()
	{
		var (Found, Value) = typeof(MyClass).GetValueAttribute("MyCustomAttribute");
		Console.WriteLine("MyCustomAttribute...");
		Console.WriteLine("Found: " + Found);
		Console.WriteLine("Value: " + Value);
	}
}

[Value("MyCustomAttribute", "hello world")]
public class MyClass { }
```

The first parameter of the `ValueAttribute` is like a key while the second parameter of the 
attribute is the actual value. Then you just use the `GetValueAttribute` extension method with the key (first parameter) and it will do the reflection for you. You can add multiple `ValueAttributes` per code member:

```cs
[Value("Name", "Array Benchmarks")]
[Value("Description", "These benchmarks do...")]
[Value("Source URL", "google.com")]
public class MyBenchmarks
{
	// code...
}
```

How does it work? It does exactly the same thing as the first example, but it just looks for
the the `ValueAttribute` that contains the matching key value. Here is the source code:

```cs
using System;
using System.Reflection;

namespace Towel
{
	/// <summary>A value-based attribute.</summary>
	[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
	public class ValueAttribute : Attribute
	{
		internal object Attribute;
		internal object Value;

		/// <summary>Creates a new value-based attribute.</summary>
		/// <param name="attribute">The attribute.</param>
		/// <param name="value">The value.</param>
		public ValueAttribute(object attribute, object value)
		{
			Attribute = attribute;
			Value = value;
		}
	}

	/// <summary>Extension methods for reflection types and <see cref="ValueAttribute"/>.</summary>
	public static class ValueAttributeExtensions
	{
		/// <summary>Gets a <see cref="ValueAttribute"/> on a <see cref="MemberInfo"/>.</summary>
		/// <param name="memberInfo">The type to get the <see cref="ValueAttribute"/> of.</param>
		/// <param name="attribute">The attribute to get the value of.</param>
		/// <returns>
		/// (<see cref="bool"/> Found, <see cref="object"/> Value)
		/// <para>- <see cref="bool"/> Found: True if the attribute was found; False if not or if multiple attributes were found (ambiguous).</para>
		/// <para>- <see cref="object"/> Value: The value if found or default if not.</para>
		/// </returns>
		public static (bool Found, object Value) GetValueAttribute(this MemberInfo memberInfo, object attribute)
		{
			_ = memberInfo ?? throw new ArgumentNullException(nameof(memberInfo));
			bool found = false;
			object value = default;
			foreach (ValueAttribute valueAttribute in memberInfo.GetCustomAttributes<ValueAttribute>())
			{
				if (ReferenceEquals(attribute, valueAttribute.Attribute) || attribute.Equals(valueAttribute.Attribute))
				{
					if (found)
					{
						return (false, default);
					}
					found = true;
					value = valueAttribute.Value;
				}
			}
			return (found, value);
		}

		/// <summary>Gets a <see cref="ValueAttribute"/> on a <see cref="ParameterInfo"/>.</summary>
		/// <param name="parameterInfo">The type to get the <see cref="ValueAttribute"/> of.</param>
		/// <param name="attribute">The attribute to get the value of.</param>
		/// <returns>
		/// (<see cref="bool"/> Found, <see cref="object"/> Value)
		/// <para>- <see cref="bool"/> Found: True if the attribute was found; False if not or if multiple attributes were found (ambiguous).</para>
		/// <para>- <see cref="object"/> Value: The value if found or default if not.</para>
		/// </returns>
		public static (bool Found, object Value) GetValueAttribute(this ParameterInfo parameterInfo, object attribute)
		{
			_ = parameterInfo ?? throw new ArgumentNullException(nameof(parameterInfo));
			bool found = false;
			object value = default;
			foreach (ValueAttribute valueAttribute in parameterInfo.GetCustomAttributes<ValueAttribute>())
			{
				if (ReferenceEquals(attribute, valueAttribute.Attribute) || attribute.Equals(valueAttribute.Attribute))
				{
					if (found)
					{
						return (false, default);
					}
					found = true;
					value = valueAttribute.Value;
				}
			}
			return (found, value);
		}
	}
}
```