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
- 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.
- CSharpIn.dll -> A snap-in written in C#, which leverages the types of commonSnappableTypes.dll
- VBSnapIn.dll : A snap-in written in Visual Basic, which leverages the types of CommonSnappableTypes.dll
- 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;
}
}