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.
No comments:
Post a Comment