The first step in building a custom attribute is to create a new cass deriving from System.Attribute.

public sealed class VehicleDescriptionAttribute : Attribute
 {
     public string Descriptpion { get; set; }
     public VehicleDescriptionAttribute(string description)
     {
         Descriptpion = description;
     }
     public VehicleDescriptionAttribute()
     {
             
     }
 }

Note: For security reasons, it is considered a .NET best practice to design all custom attributes as sealed.

Applying Custom Attributes

[VehicleDescriptionAttribute(Descriptpion = "My rocking harley")]
public class MotorCycle { }

Restricting Attribute Usage

By default, custom attributes can be applied to just about any aspect of your code (classes,methods,properties,etc).

[VehicleDescriptionAttribute(Descriptpion = "A very long, slow")]
public class Winnebago
 {
     [VehicleDescription(Descriptpion = "My rocking CD player")]
     public void PlayMusic(bool On)
     {
    }
 }

If you want to constrain the scope of a custom attribute, you will need to apply the [AttributeUsage] attribute on the definition of your custom attribute.

To establish that the [VehicleDescription] attribute can be applied only once on a class or structure, you can update the VehicleDescriptionAttribute as follows.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)]
public sealed class VehicleDescriptionAttribute : System.Attribute { }

[AttributeUsage] also allows you to optionally set a named property (AllowMultiple) that specifies whether the attribute can applied more than once on the same item (the default is false). As well, [AttributeUsage] allows you to establish whether attribute should be inherited by derived classes using the Inherited named property (the default is true).

Assembly-Level Attributes


It's also possible to apply attributes on all types within a given assembly using the

[assembly:Tag] 

ex:

[assembly: CLSCompliant]
namespace AttributedCarLibrary;

Reflecting on Attributes Using Early Binding


If you want to make use of early binding, you'll require the client application to have a compile-time definition of the attribute in question.

static void ReflectOnAttributesUsingEarlyBinding()
 {
     Type t = typeof(Winnebago);
     object[] customAtts = t.GetCustomAttributes(false);
     foreach(VehicleDescriptionAttribute v in customAtts)
     {
         Console.WriteLine(v.Descriptpion);
     }
 }

Reflecting on Attributes Using Late Binding


It's also possible to make use of dynamic loading and late binding to reflect over attributes.

static void ReflectOnAttributesUsingLateBinding()
 {
     Assembly asm = Assembly.LoadFrom("AttributesCarLibrary");
     // Get tpype info of V...
     Type v = asm.GetType("AttributedCarLibrary.VehicleDescriptionAttribute");
     // Get type info Description property
     PropertyInfo p = v.GetProperty("Description");
     // Get all types in the assembly.
     Type[] types = asm.GetTypes(); ;
     // Iterate over each type and obtain any V
     foreach (Type t in types)
     {
         object[] objs = t.GetCustomAttributes(v, false);
         // Iterate over each VehicleDesciprtionAttribute and print the description using late binding.
         foreach(object o in objs)
         {
             Console.WriteLine(t.Name+" "+p.GetValue(o,null));
         }
     }
 }

Building an Extendable Application


  1. CommonSnappableTypes.dll => This assembly contains type definitions that will be used by each snap-in object and will be directly referenced by the Windows Forms application.
  2. CSharpIn.dll -> A snap-in written in C#, which leverages the types of commonSnappableTypes.dll
  3. VBSnapIn.dll : A snap-in written in Visual Basic, which leverages the types of CommonSnappableTypes.dll
  4. MyExtendableApp.exe : A console application that may be extended by the functionality of each snap-in.

A pluging system: main app loads DLLs at runtime without knowing them at compile time.

// Classlib -> Release
namespace CommonSnappableTypes
 {
     // Every plugin must implement this.
     public interface IAppFunctionality
     {
         void DoIt();
     }
 }
 
// Reference the CommonSnappableTypes
namespace CSharpSnapIn
 {
     [CompanyInfo(Name = "DotNetGuard")] // optional
     public class CSharpModule : IAppFunctionality
     {
         public void DoIt()
         {
             Console.WriteLine("You have just web the C# snap-it");
         }
     }
 }
 
 
 
// References the CommonSnappableTypes
// ExtendableApp
string pluginName = Console.ReadLine();
string pluginPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,$"{pluginName}.dll");
// STEP 1 - Load the assembly at runtime.
Assembly pluginAssembly = Assembly.LoadFrom(pluginPath);
// STEP 2 - Find the type that implements IAppFunctionality
Type[] types = pluginAssembly.GetTypes();
Type pluginType = null;
foreach(Type t in types)
 {
     if (t.GetInterface(nameof(IAppFunctionality)) != null)
     {
         pluginType = t;
         break;
     }
 }
if (pluginType == null)
 {
     Console.WriteLine("No IAppFunctionality found in this dll");
     return;
 }
// STEP 3 - Create an instance and call DoIt()
IAppFunctionality p = (IAppFunctionality)Activator.CreateInstance(pluginType);
p.DoIt();

The Role of the C# dynamic Keyword


You can consider the "dynamic" keyword a specialized form of System.Object, in that any value can be assigned to a dynamic data type.

What makes a dynamic variable vastly different from a variable declared implicitly or via a System.Object reference is that it is not strongly typed. Said another way, dynamic data is not statically typed. As for as the C# compiler is concerned, a data point declared with the dynamic keyword can be assigned any initial value at all and can be reassigned to any new value during its lifetime.

static void ChangeDynamicDataType()
 {
     dynamic t = "Hello";
     Console.WriteLine(t.GetType());
     t = false;
     Console.WriteLine(t.GetType());
     t = new List<int>();
     Console.WriteLine(t.GetType());
 }

Calling members on Dynamically Declared Data

The validity of the members you specify will not be checked by the compiler.

static void InvokeMembersOnDynamicData()
 {
     dynamic t = "Hello";
     Console.WriteLine(t.ToUpper());
     Console.WriteLine(t.toUpper()); // Compiler says OK! Runtime error.
     Console.WriteLine(t.Foo(10,"ee",DateTime.Now)); // Compiler says OK! Runtime error.
 }

The Scope of the dynamic Keyword

Implicitly typed data (var) is possible only for local variables in a member scope. The "var" keyword can never be used as a return value, a parameter, or a member of a class/structure. This is not the case with the "dynamic" keyword.

class VerifyDynamicClass
 {
     // A dynamic field.
     private static dynamic _myDynamicField;
     // A dynamic property.
     public dynamic DynamicProperty { get; set; }
     // A dynamic return type and a dynamic parameter type.
     public dynamic DynamicMethod(dynamic dynamicParam)
     {
         // A dynamic local variable.
         dynamic dynamicLocalVar = "Local variable";
         int myInt = 10;
         if (dynamicParam is int)
         {
             return dynamicLocalVar;
         }
         else
         {
             return myInt;
         }
     }
 }

The Role of the Dynamic Language Runtime

Since the release of .NET 4.0, the CLR was supplemented with a complementary runtime environment DLR. In a nutshell, DLR allows a dynamic language the ability to discover types completely at runtime with no compile-time checks.

Simplifying Late-Bound Class Using Dynamic Types

One instance where you might decide to use the "dynamic" keyword is when you are working with reflection services, specifically when making late-bound method calls.

static void InvokeMethodWithDynamicKeyword(Assembly asm)
 {
     try
     {
         Type miniVan = asm.GetType("CarLibrary.MiniVan");
         dynamic obj = Activator.CreateInstance(miniVan);
         obj.TurboBoost();
     }
     catch (Exception)
     {
      throw;
     }
 }