public static yyy operator + ( yyy x , yyy y) {
System.Console.WriteLine(“operator + “ + x.i + “ “ + y.i);
yyy z = new yyy(x.i+y.i);
return z;
} } Output
Operator + 10 5 Operator + 15 2 17
The only change is d = a + b + c. C# gets easily confused with complex statements so it does not read all of it. It sees two operators on the same line. In this case, the same plus. An internal rule tells it to read the plus left to right i.e. it will only see a + b. It will call the operator + with x.i as 10 and y.i as 5 because a’s i is 10 and b’s i is 5. This will create a temporary object like yyy whose i is 15, lets call it zz.
The object z is very different from zz. C# then evaluates zz + c. Thus x.i will display 15 and y.i will have the value of c.i i.e. 2. To support multiple invocations of the operator on a single line, the code does not change.
13
Collection Objects
Earlier we used an array in a foreach. If the array had three members, the foreach got executed three times and each time the variable i had a different value. The concept described below is called a collection class. It is a class that returns a value each time till the values run out, thus making it easier for us to iterate through the array.
a.cs
public class zzz {
public static void Main() {
yyy f = new yyy();
foreach (string i in f) {
} } }
class yyy {
}
Compiler Error
a.cs(6,1): error CS1579: foreach statement cannot operate on variables of type ‘yyy’ because
‘yyy’ does not contain a definition for ‘GetEnumerator’, or it is inaccessible
To use yyy in a foreach as a collection class, foreach requires a function GetEnumerator.
a.cs
public class zzz {
public static void Main() {
yyy f = new yyy();
foreach (string i in f) {
} } }
class yyy {
public int GetEnumerator() {
} }
Compiler Error
a.cs(6,1): error CS1579: foreach statement cannot operate on variables of type 'yyy' because 'int' does not contain a definition for 'MoveNext', or it is inaccessible
a.cs(13,12): error CS0161: 'yyy.GetEnumerator()': not all code paths return a value
Foreach obviously tries to execute the function called GetEnumerator. This function should not return an int but something else as the error suggests.
a.cs
using System.Collections;
public class zzz {
public static void Main() {
yyy f = new yyy();
foreach (string i in f) {
} } }
class yyy {
public IEnumerator GetEnumerator() {
return new xxx();
} }
class xxx : IEnumerator {
}
Compiler Error
a.cs(19,7): error CS0535: ‘xxx’ does not implement interface member
‘System.Collections.IEnumerator.MoveNext()’
a.cs(19,7): error CS0535: ‘xxx’ does not implement interface member
‘System.Collections.IEnumerator.Reset()’
a.cs(19,7): error CS0535: ‘xxx’ does not implement interface member
‘System.Collections.IEnumerator.Current’
IEnumerator is an interface which has three functions MoveNext, Reset and Current and xxx has to implement all of them to remove the compiler errors.
a.cs
using System.Collections;
public class zzz {
public static void Main() {
yyy f = new yyy();
foreach (string i in f) {
System.Console.WriteLine(i);
} } }
class yyy {
public IEnumerator GetEnumerator() {
return new xxx();
} }
class xxx : IEnumerator {
public bool MoveNext() {
System.Console.WriteLine(“MoveNext”);
return true;
}
public void Reset() {
System.Console.WriteLine(“Reset”);
}
public object Current {
get {
System.Console.WriteLine(“Current”);
return “hi”;
} } }
Run the program and you will notice that the output does not stop. It goes on forever.
IEnumerator is an interface which belongs to the namespace System.Collections. foreach first calls the function GetEnumerator from yyy. It expects this function to return an object like IEnumerator. It then calls the function MoveNext from this returned object. If MoveNext returns true it knows that there is some data to be read and it calls the property Current to access this data. From Current the get accessor gets called which always returns “hi” in our case. Then MoveNext gets called and if it returns false, we quit out of the foreach statement. As MoveNext always returns true, we go into an indefinite loop.
a.cs
using System.Collections;
public class zzz {
public static void Main() {
yyy f = new yyy();
foreach (string i in f) {
System.Console.WriteLine(i);
} } }
class yyy {
public IEnumerator GetEnumerator() {
return new xxx();
} }
class xxx : IEnumerator {
public string [] a = new string[3] {“hi” , “bye” ,”no”};
public int i = -1;
public bool MoveNext() {
i++;
System.Console.WriteLine(“MoveNext” + i);
if ( i == 3) return false;
else
return true;
}
public void Reset() {
System.Console.WriteLine(“Reset”);
}
public object Current {
get {
System.Console.WriteLine(“Current “ + a[i]);
return a[i];
} } } Output MoveNext0 Current hi hi
MoveNext1 Current bye bye
MoveNext2 Current no no
MoveNext3
We have created an array a which has 3 members and initialized them respectively to hi, bye and no by giving the strings in {} immediately after the new. Each time MoveNext gets called the variable i is increased by 1. If the value of i is 3, we have no more strings to return and thus we return false, else we return true. The variable i keeps track of how many times the function MoveNext is being called. As
MoveNext returns true, Current gets called which returns a string from the array using i as the offset.
Thus we can iterate through the entire array depending upon the length.
a.cs
using System.Collections;
public class zzz {
public static void Main() {
yyy f = new yyy(“This is Great”);
foreach (string i in f) {
class xxx : IEnumerator {
public string [] a;
public xxx(string t3)
System.Console.WriteLine(“MoveNext “ + i);
if ( i == a.Length)
System.Console.WriteLine(“Current “ + a[i]);
return a[i];
} } }
Output MoveNext 0 Current This This
MoveNext 1 Current is is
MoveNext 2 Current Great Great
MoveNext 3
Pretty big program. At the time of creating a yyy object we are passing a string to the constructor. Thus the yyy constructor gets called first. The constructor stores this string in variable t. The foreach statement calls
GetEnumerator which now creates a xxx object passing it the string through t. The constructor of xxx now gets called. Every string class has a member function called Split. Split will break up a string on certain characters which we call delimiters. In this case, we want our string to be broken up whenever we encounter a space. The Split function requires an array of chars which it can use as a delimiter. The reason it requires an array is because we may have more than one char that we would like to break the string on. Like earlier, the array a now contains the array of strings. The last change is the condition in the if statement. Earlier we used a constant number, now we use a member, Length, of an array which stores the length of the array or the number of members. Thus the class yyy can now be used as a collection class which enumerates the individual words in the string. The function Reset for some reason never ever gets called.
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.