14
Attributes, The Reflection API And Conditionals
Attributes
a.cs
class zzz
{
public static void Main()
{
}
}
[vijay]
class yyy
{
}
Compiler Error
a.cs(7,2): error CS0246: The type or namespace name 'vijay'
could not be found (are you missing a using directive or an assembly
reference?)
Anything in a square bracket is called an attribute. We
tried to create an attribute called vijay, which C#, for some reason, does not
seem to recognize.
a.cs
class zzz
{
public static void Main()
{
}
}
class vijay : System.Attribute
{
}
[vijay]
class yyy
{
}
All that we have done is created a class vijay that has been
derived from the class System.Attribute and the error simply disappears. Thus
an attribute is simply a class that derives from System.Attribute. To
understand attributes lets take an example with structures.
a.cs
class zzz
{
public static void Main()
{
yyy a = new yyy();
a.i = 65536+512+3;
System.Console.WriteLine(a.i + " " + a.j + "
" + a.k);
}
}
struct yyy {
public int i;
public short j;
public byte k;
}
Output
66051 0 0
A simple revision once again. We have created a structure a,
that looks like yyy and initialized only one member i. Hence we see the
warnings. The other members j and k get a default value of zero.
a.cs
using System;
class zzz
{
unsafe public static void Main()
{
Console.WriteLine(sizeof(byte) + " " +
sizeof(short) + " " + sizeof(int) + " " + sizeof(long));
}
}
>csc a.cs
Compiler Error
a.cs(4,27): error CS0227: Unsafe code may only appear if
compiling with /unsafe
The error here says that you have to use the /unsafe option
while compiling any unsafe code.
>csc a.cs /unsafe
Output
1 2 4 8
We shall explain the modifier unsafe in the next chapter.
Sizeof tells us how much memory C# allocates for a data type. A byte is
allocated one memory location, short 2, int 4 and a long 8.
a.cs
using System.Runtime.InteropServices;
class zzz
{
public static void Main()
{
yyy a = new yyy();
a.i = 65536+512+3;
System.Console.WriteLine(a.i + “ “ + a.j + “ “ + a.k);
}
}
[StructLayout(LayoutKind.Explicit)]
struct yyy
{
[FieldOffset(0)] public int i;
[FieldOffset(0)] public short j;
[FieldOffset(0)] public byte k;
}
Output
66051 515 3
We are using an attribute
StructLayout that belongs to the namespace System.Runtime.InteropServices. In
the earlier program, we had used an attribute called vijay. Thus, StructLayout
is a class derived from Attribute. We are passing a parameter LayoutKind.Explicit
to it. The output now differs dramatically.
Every variable is stored in memory. FieldOffset indicates
the starting position of the variable within the memory location. Offset of 0
will position i, j, and k, all three variables at the same memory address of a.
Explicit requires FieldOffset to be mentioned as we are explicitly laying the
order for the variables held in the strucuture.LayoutKind.Sequential and
LayoutKind.Auto gives different memory locations to each of the variable.
We will explain the reasons a little later in the coming
chapter ‘Unsafe Code’. We have seen how important attributes are so lets delve
deeper into them.
a.cs
class zzz
{
public static void Main()
{
}
}
class vijayAttribute : System.Attribute
{
}
[vijay]
class yyy
{
}
[vijayAttribute]
class yyy1
{
}
We are allowed a little leeway in the name of the attribute.
By convention, the attribute class should end with the word Attribute and when
we use the attribute, the name attribute is optional.
a.cs
class zzz
{
public static void Main()
{
}
}
class vijay : System.Attribute
{
}
[vijay("hi")]
class yyy
{
}
Compiler Error
a.cs(10,2): error CS1501: No overload for method 'vijay'
takes '1' arguments
We had used the attribute StructLayout earlier where we
passed a parameter. When we do the same thing with our attribute vijay, we get
the above error.
a.cs
class zzz
{
public static void Main()
{
}
}
class vijay : System.Attribute
{
public vijay(string s)
{
}
}
[vijay("hi")]
class yyy
{
}
We forgot to add a constructor that accepts a string as a
parameter. If we had passed a number to our attribute vijay, we would have to
create a constructor that accepts an int. Thus if we pass 2 parameters to
vijay, we need the appropriate constructor.
a.cs
class zzz
{
public static void Main()
{
}
}
class vijay : System.Attribute
{
public vijay(string s ,int i)
{
}
}
[vijay("hi",10,mukhi = 200)]
class yyy
{
}
Compiler Error
a.cs(13,16): error CS0103: The name 'mukhi' does not exist
in the class or namespace 'vijay'
What we tried to do is, take a word called mukhi and
initialize it to 200. C# comes back and tells us that it does not know what
mukhi is.
a.cs
class zzz
{
public static void Main()
{
}
}
class vijay : System.Attribute
{
public vijay(string s ,int i)
{
}
public int mukhi;
}
[vijay("hi",10,mukhi = 200)]
class yyy
{
}
mukhi, now, is called a named parameter. It can also be
termed as a property.
a.cs
class zzz
{
public static void Main()
{
}
}
class vijay : System.Attribute
{
public vijay(string s ,int i)
{
}
public int mukhi;
public string sonal
{
get
{
return "ss";
}
set
{
;
}
}
}
[vijay("hi",10, mukhi = 200, sonal =
"bye")]
class yyy
{
}
A named parameter is a non-static field or a non-readonly
property. A positional parameter is what we pass on to a constructor. We have 2
positional parameters as our constructor has two parameters and mukhi and sonal
are our named parameters. The named parameters come after the positional ones.
The positional parameter's order is important, but the named parameters can be
in any order. If we don't follow this rule we will get an error as follows.
Compiler Error
a.cs(19,22): error CS1016: Named attribute argument expected
When we place the attribute before a function, the error
disappears.
a.cs
using System.Runtime.InteropServices;
using System;
class zzz
{
public static void Main()
{
}
}
[AttributeUsage(AttributeTargets.Class)]
class vijay : System.Attribute
{
public vijay(string s ,int i)
{
}
public int mukhi;
public string sonal
{
get { return "ss"; }
set { ; }
}
}
class yyy
{
[vijay("hi",10, sonal = "bye",mukhi = 200
)]
public void abc() {}
}
Compiler Error
a.cs(24,2): error CS0592: Attribute 'vijay' is not valid on
this declaration type. It is valid on 'class' declarations only.
AttributeUsage is one more attribute class derived from
Attribute. It gives us the option to decide where the user can use the Attribute.
The parameter in this case is class and hence we can use it only in front of a
class and not in front of a method. The default is anywhere.
The Reflection API
Reflection or Introspection is when you look within to find
out about your true self. In the same way we need a method by means of which,
our program can find out all about a class. We need to know how many methods,
properties etc while our program is executing or running. This distinction is
important and we could always read the documentation if we wanted to know more
about the functionality of a class. But, C# gives us a large number of
functions that tell us the innards of a class. These functions put together
have to be used in a certain way. The functions have to be called in a certain order
and the parameters to them have to conform to certain data types. This concept
is called an API or a Application Program Interface. In short, an API is how a
programmer uses functions to get a desired result.
a.cs
using System;
class zzz
{
public static void Main()
{
Type m;
m = typeof(int);
System.Console.WriteLine(m.Name + " " +
m.FullName);
m = typeof(System.Int32);
System.Console.WriteLine(m.Name + " " +
m.FullName);
m = typeof(yyy);
System.Console.WriteLine(m.Name + " " +
m.FullName);
}
}
class yyy
{
}
Output
Int32 System.Int32
Int32 System.Int32
yyy yyy
Typeof is a keyword. It needs a class name as a parameter.
In the first case, we are specifying a class called int. typeof returns an
object that looks like Type. This class has two members Name, which gives the
name of the class and FullName which is the name preceded with the name of the
Namespace. When we use int as the name of the class, the member Name does not
display int but Int32; we mentioned earlier int is an alias for a structure
Int32 in the System namespace. This is what FullName tells us. We thus have
visible proof that int is an alias for a structure. yyy is class belonging to
no namespace and hence the Name and FullName members are similar.
a.cs
using System;
using System.Reflection;
class zzz
{
public static void Main()
{
Type m = typeof(yyy);
MemberInfo [] n;
n = m.GetMembers();
Console.WriteLine(n.Length);
foreach ( MemberInfo a in n)
{
Console.WriteLine(a.Name);
}
}
}
class yyy {
public void abc() {}
private int pqr( int i ) { return 0;}
protected string xyz (string g , int p) {return
"";}
}
Output
6
GetHashCode
Equals
ToString
abc
GetType
.ctor
We are now displaying the members of a class yyy. The class
Type has a function called GetMembers that returns an array of type MemberInfo.
Every array has a field called Length that returns the size of the array. In
our specific case it is 6. We then use a foreach to run through each member of
the MemberInfo array and are displaying the name of each function using the
field Name from the class MemberInfo.
a.cs
using System;
using System.Reflection;
class zzz
{
public static void Main()
{
Type m = typeof(yyy);
MemberInfo [] n;
n = m.GetMembers();
Console.WriteLine(n.Length);
foreach ( MemberInfo a in n)
{
Console.WriteLine((MemberInfo)a + " " +
a.DeclaringType);
}
}
}
class yyy {
public int i;
public void abc() {}
public int pqr( int i ) { return 0;}
public string xyz (string g , int p) {return "";}
}
Output
9
Int32 i yyy
Int32 GetHashCode() System.Object
Boolean Equals(System.Object) System.Object
System.String ToString() System.Object
Void abc() yyy
Int32 pqr(Int32) yyy
System.String xyz(System.String, Int32) yyy
System.Type GetType() System.Object
Void .ctor() yyy
The first concept you need to be clear with is that we can
inspect only details of public members and protected or private like pqr and
xyz. Also variables are part of the members of a class. The MemberInfo object
has a ToString function that displays the entire function in all its glory
including parameters and their data types. The names of the parameter variables
are however not being displayed. The DeclaringType member returns the class
name that the member belongs to. Thus we can differentiate which class created
the function.
Let us now display the attributes used on a class.
a.cs
using System.Runtime.InteropServices;
using System;
using System.Reflection;
class zzz
{
public static void Main()
{
Type m;
m = typeof(yyy);
System.Console.WriteLine(m.Name);
foreach(object a in m.GetCustomAttributes (true))
Console.WriteLine(a);
}
}
[AttributeUsage(AttributeTargets.All)]
class vijay : System.Attribute
{
string s1,s2;int i1;
public int mukhi;
public override string ToString()
{
return s1+" " + s2+" " + i1 + "
" + mukhi;
}
public vijay(string s ,int i)
{
s1=s;i1=i;
}
public string sonal
{
get { return s2; }
set { s2 = value; }
}
}
[vijay("hi1",10, sonal = "bye1",mukhi =
200 )]
class yyy
{
[vijay("hi2",100, sonal = "bye2",mukhi =
2000 )]
public void abc() {}
[vijay("hi3",1000, sonal = "bye3",mukhi =
2 )]
public int i;
}
Output
yyy
hi1 bye1 10 200
GetCustomAttributes takes a boolean as parameter and returns
an array of objects. The ToString function of the attribute class gets called
which will decide what string the attribute stands for.
a.cs
using System.Runtime.InteropServices;
using System;
using System.Reflection;
class zzz
{
public static void Main()
{
Type m;
m = typeof(yyy);
System.Console.WriteLine(m.Name);
foreach(MethodInfo a in m.GetMethods())
{
object [] b = a.GetCustomAttributes(true);
foreach(Attribute c in b)
{
Console.WriteLine(c);
}
}
}
}
[AttributeUsage(AttributeTargets.All)]
class vijay : System.Attribute
{
string s1,s2;int i1;
public int mukhi;
public override string ToString()
{
return s1+" " + s2+" " + i1 + "
" + mukhi;
}
public vijay(string s ,int i)
{
s1=s;i1=i;
}
public string sonal
{
get { return s2; }
set { s2 = value; }
}
}
[vijay("hi1",10, sonal = "bye1",mukhi =
200 )]
class yyy
{
[vijay("hi2",100, sonal = "bye2",mukhi =
2000 )]
public void abc() {}
[vijay("hi3",1000, sonal = "bye3",mukhi =
2 )]
public void pqr() {}
}
Output
yyy
hi2 bye2 100 2000
hi3 bye3 1000 2
The object m looks like Type. As explained earlier, we are
calling a function called GetMethods which returns an array of MethodInfo's. a
loops through each one. We have two methods and the foreach gets executed
twice. Once for abc and then for pqr. The GetCustomAttributes also exists in a
MethodInfo class that returns an array of objects representing our attributes.
We iterate through each, displaying what the ToString function returns. As we
have only one attribute per function, the second for each gets executed only once.
a.cs
using System.Runtime.InteropServices;
using System;
using System.Reflection;
class zzz
{
public static void Main()
{
Type m;
m = typeof(yyy);
System.Console.WriteLine(m.Name);
foreach(MethodInfo a in m.GetMethods())
{
object [] b = a.GetCustomAttributes(true);
foreach(Attribute c in b)
{
if ( c is vijay )
Console.WriteLine(c);
}
}
}
}
[AttributeUsage(AttributeTargets.All)]
class vijay : System.Attribute
{
string s1,s2;int i1;
public int mukhi;
public override string ToString()
{
return s1+" " + s2+" " + i1 + "
" + mukhi;
}
public vijay(string s ,int i)
{
s1=s;i1=i;
}
public string sonal
{
get { return s2; }
set { s2 = value; }
}
}
[vijay("hi1",10, sonal = "bye1",mukhi =
200 )]
class yyy
{
[vijay("hi2",100, sonal = "bye2",mukhi =
2000 )]
public void abc() {}
[vijay("hi3",1000, sonal = "bye3",mukhi =
2 )]
public void pqr() {}
}
There is no change at all in the output. A function can be
decorated with as many attributes as you like. We would like to filter out
certain attributes.
a.cs
using System.Runtime.InteropServices;
using System;
using System.Reflection;
class zzz
{
public static void Main()
{
Type m;
m = typeof(yyy);
System.Console.WriteLine(m.Name);
foreach(MethodInfo a in m.GetMethods())
{
object [] b = a.GetCustomAttributes(true);
foreach(Attribute c in b)
{
if ( c is vijay )
Console.WriteLine(c);
}
}
}
}
class vijay : System.Attribute
{
public override string ToString()
{
return "vijay";
}
}
class vijay1 : System.Attribute
{
public override string ToString()
{
return "vijay";
}
}
class yyy
{
[vijay()]
public void abc() {}
[vijay()]
[vijay1()]
public void pqr() {}
}
Output
yyy
vijay
vijay
We have two attribute classes vijay and vijay1. The function
pqr has been decorated with 2 attributes whereas abc with only one. However we
do not see vijay1 in the output as the 'c is vijay' makes the if statement true
only for the attribute vijay and not vijay1. For the function pqr
GetCustomAttributes returns an array of size two, but the if statement is true
only for one of them, the one with the attribute name vijay. This is because of
the 'is'.
a.cs
using System;
class zzz
{
public static void Main()
{
yyy a = new yyy();
if ( a is yyy)
Console.WriteLine("a yyy");
xxx b = new yyy();
if ( b is xxx)
Console.WriteLine("b xxx");
if ( b is yyy)
Console.WriteLine("b yyy");
int d = 10;
if ( d is yyy)
Console.WriteLine("b yyy");
}
}
class xxx
{
}
class yyy : xxx
{
}
Output
a yyy
b xxx
b yyy
We would like to know the data type of an object at runtime.
C# offers you a keyword 'is' that lets you check the data type of an object. a
looks like yyy and 'is' results in true. B looks like xxx but is initialized to
a new yyy. Thus it doubles up for a yyy and a xxx resulting in the next two
is's returning true. D is an int and not a yyy, so the last 'is' is false.
Attributes Revisited
Positional parameters are a must whereas names parameters
are optional. Attribute parameters can be a bool, byte, char, short, int, long,
float and double. These are the simple types that C# supports. Other data types
are string, enums, objects arrays etc.
Attribute usage has a position parameter which specifies the
elements where the attribute can be used. The default is All. It also has one
named parameter called AllowMultiple.
a.cs
using System.Runtime.InteropServices;
using System;
class zzz
{
public static void Main()
{
}
}
[AttributeUsage(AttributeTargets.All)]
class vijay : System.Attribute
{
public vijay(string s)
{
}
}
class yyy
{
[vijay("hi")][vijay("hi1")]
public void abc() {}
}
Compiler Error
a.cs(18,15): error CS0579: Duplicate 'vijay' attribute
By default we cannot use the same attribute twice on any
entity.
a.cs
using System.Runtime.InteropServices;
using System;
class zzz
{
public static void Main()
{
}
}
[AttributeUsage(AttributeTargets.All,AllowMultiple=true)]
class vijay : System.Attribute
{
public vijay(string s)
{
}
}
class yyy
{
[vijay("hi")][vijay("hi1")]
[vijay("hi2") , vijay("hi3")]
public void abc() {}
}
We get no error as by default the AllowMultiple named
parameter has a value of false. If we set its value to true, we are allowed to
use multiple attributes on any entity. The above two forms are similar and
either one can be used. Attribute permits us to set declarative information for
various program entities for use by someone else at run time.
Conditionals
a.cs
using System.Diagnostics;
using System;
class zzz
{
public static void Main()
{
yyy a = new yyy();
a.abc();
}
}
class yyy
{
[Conditional("vijay")]
public void abc()
{
Console.WriteLine("abc");
}
}
When we run the above program we get no output at all. In
other words the function abc does not get called at all. This is inspite of
writing a.abc().
a.cs
#define vijay
using System.Diagnostics;
using System;
class zzz
{
public static void Main()
{
yyy a = new yyy();
a.abc();
}
}
class yyy
{
[Conditional("vijay")]
public void abc()
{
Console.WriteLine("abc");
}
}
Output
abc
Any line beginning with a # is read by the C# pre-processor,
a program that starts before the C# compiler starts. It has words like #define
which creates a variable or word called vijay. In a programming language, a
variable has to have a value, but in the preprocessor scheme of things, it
may/maynot have a value. However if we do not give it a value, like in this
case, the variable is only set to have been defined or created. Anything in []
brackets is an Attribute class. Earlier we had not created a variable vijay and
hence the entire code of abc was left out of the executable file. Not only
that, but all calls to function abc were eliminated from our code. All of this
by defining or not defining a variable vijay. This is what we passed as the
attribute to Conditional. We can create functions that are omitted during
compilation depending upon a preprocessing symbol.
When we write code, we add a lot of code for debugging
purposes. This code is to help the programmer debug code. After a function
works, it is error free, we do not require any of this debugging code. One way
to eliminate this debugging code is by making the functions conditional. The
resulting code is called a 'Retail build' and the debugging version, obviously
a 'Debug build'. A Retail build is much smaller in size and obviously much
faster. The #define has to be at the start of code.
Another way of achieving the same result is by eliminating
the #define from the code and creating a preprocessor symbol as an option to
the compiler.
>csc a.cs /d:vijay
The compiler option is /d and the colon is part of the
syntax. Following the colon is the name of the preprocessor symbol. In this
case, it is vijay. We’ve discussed preprocessors in one of the earlier
chapters.
a.cs
using System;
class zzz
{
public static void Main()
{
yyy a = new yyy();
a.abc();
}
}
class yyy
{
[Obsolete]
public void abc()
{
Console.WriteLine("abc");
}
}
Compiler Error
a.cs(7,1): warning CS0612: 'yyy.abc()' is obsolete
Output
abc
Many a times we create functions in a class which we would not want the user to use, as these functions were useful years ago, but are now obsolete. The only way to warn the user that some time in the future we will no longer support these functions is by marking them with the attribute Obsolete. We see the warning as displayed above but the program runs as normal.