Friday, April 12, 2019

C# Properties and Indexers

Properties

Properties are a natural extension to fields. Very few programming languages support the notion of a property. Unlike a variable, a property is not stored in a memory location. It is made up of functions. Thus even though a property and a field share the same syntax a property has the advantage that code gets called. When we initialize a variable, no code in our class gets called. We are not able to execute any code for a variable access or initialization at all. In the case of a property, we can execute tons of code. This is one singular reason for the popularity of a product like Visual Basic - the use of properties. One simple example is setting the value of a variable. If it is through a variable, we have no control over the value used. If the same access is through a property, the programmer has no inkling of whether it is a property or a variable, we can build range checks to make sure that the variable does not cross certain bounds.

Lets start by creating a simple property. A property is a member of a class. It behaves like a variable for the user.


a.cs
public class zzz
{
public static void Main()
{
}
}
public class aa
{
public int ff {
}
}

Compiler Error
a.cs(9,12): error CS0548: ‘aa.ff’ : property or indexer must have at least one accessor

We have tried to create a property called ff which is of type int. We get an error because a property is used either on the left or the right of an equal to sign. If we had created a variable ff, we would like to write  a statement as gg = ff + 9. Here ff should return some value which is of the data type int.

a.cs
public class zzz
{
public static void Main()
{
aa a = new aa();
int gg = a.ff + 9;
System.Console.WriteLine(gg);
}
}
public class aa
{
            public int ff {
            get
            {
            System.Console.WriteLine(“in get”);
           
            return 12;
            }
            }
}

Output
in get
21

A property should have at least one accessor, in our case, a get as we want to read the value of the property. Thus a.ff calls the get accessor which returns an int, in this case 12. If we did not have access to the code of the class aa, we would have assumed ff to have been a variable.

a.cs
public class zzz
{
public static void Main()
{
aa a = new aa();
a.ff = 19;
System.Console.WriteLine(a.ff);
}
}
public class aa
{
public int ff {
            get
            {
            System.Console.WriteLine(“in get”);
            return 12;
            }
            set
            {
            System.Console.WriteLine(value);
            }
}
}

Output
19
in get
12

A variable can also be used on the left-hand side of the equalto sign. In this case we are writing or changing the value of the variable. We are passing it some value. If it is a property, ff in our case, a.ff = 19 will call the accessor set. The set accessor  has a free variable available in it called value. It gets created automatically, we do not create this variable. In our case, this has the value 19, which we are displaying in WriteLine. Then to display the value of the property ff, the get needs to be called again. The get always returns the same answer as the set does not store the value of the variable anywhere. To resolve this issue, we do the following.

a.cs
public class zzz
{
public static void Main()
{
aa a = new aa();
a.ff = 19;
System.Console.WriteLine(a.ff);
}
}
public class aa
{
int f1;
public int ff {
            get
            {
            System.Console.WriteLine(“in get”);
            return f1;
            }
            set
            {
            System.Console.WriteLine(“in set “ + value);
            f1 = value;
            }
            }
}

Output
in set 19
in get
19

To implement a property in real life, we create a public variable which will hold the value of the property. This variable f1 will have the same data type as the property i.e. an int in our case. In the get, we return f1 and in the set we initialize f1 to value. This is the simplest case possible.

The reason we use a property and not a variable is because if we change the value of a variable/field,  then code in our class is not aware of the change. Also we have no control over what values the variable will contain. The user can change them to whatever he/she likes and we cannot implement range checks on the variable. Also the user may want to associate some action with the changes in the value of the variable. Using a property, reading or writing to the variable also can be monitored.

a.cs
public class zzz
{
public static void Main()
{
aa a = new aa();
a.ff = 19;
System.Console.WriteLine(a.ff);
}
}
public class aa
{
int f1;
public int ff {
            get
            {
            System.Console.WriteLine(“in get”);
            return f1;
            }
}
}

Compiler Error
a.cs(6,1): error CS0200: Property or indexer ‘aa.ff’ cannot be assigned to — it is read only

You are allowed to declare a property readonly by omitting the set accessor. No one is now allowed to change the value of the property. It now behaves as a const or readonly field.

a.cs
public class zzz
{
public static void Main()
{
aa a = new aa();
a.ff = 19;
}
}
public class aa
{
int f1;
public int ff {
            set
            {
            System.Console.WriteLine(“in set “ + value);
            f1 = value;
            }
}
}

Output
in set 19

Theoretically, you can have a property which is write only i.e. only with a set  accessor. With set, you can change the value of ff but it is of limited use because you can never access the value of ff. A property differs from a field by ending with {}.

a.cs
public class zzz
{
public static void Main()
{
}
}
public class aa
{
public int ff {
            set
            {
            }
}
public int ff {
            get
            {
            }
}
}

Compiler Error
a.cs(12,12): error CS0102: The class ‘aa’ already contains a definition for ‘ff’

You cannot create a property in 2 separate bits and pieces. It has to be in one whole. This is part of the syntax. The above creates two properties, both called ff, the first one being write only, the second, read only. The compiler tells you that you cannot create two properties by the same name.

a.cs
public class zzz
{
public static void Main()
{
}
}
public class aa
{
private int ff;
public int ff {
            get {
            }
            set {
            }
}
}

Compiler Error
a.cs(10,12): error CS0102: The class ‘aa’ already contains a definition for ‘ff’

You obviously cannot have a property and variable with the same name. The compiler would not know whether to invoke the property or the field. They both are stored in the same namespace.

a.cs
class zzz
{
public static void Main()
{
yyy.i = 20;
System.Console.WriteLine(yyy.i);
}
}
class yyy
{
public static int i
{
get {
System.Console.WriteLine(“get”);
return 10;
}
set {
System.Console.WriteLine(“set “ + value);
            }
}
}

Output
set 20
get
10

The rules of static apply to properties also. Like variable we access them using the class and not the instance. Everything that we have learned about static in the past applies to properties also.

a.cs
class zzz
{
public static void Main()
{
yyy a = new yyy();
a.i = 100;
System.Console.WriteLine(a.i);
}
}
abstract class xxx
{
public abstract int i
{
get ;
set ;
}
}
class yyy : xxx
{
public override int i {
get
{
System.Console.WriteLine(“get”);
return 10;
}
set
{
System.Console.WriteLine(“set “ + value);
}
}
}

Output
set 100
get
10

The abstract property i in class xxx carries no code at all. The get and set accessors are simply represented by a semicolon. In the derived class, we must implement both the get and the set accessors. If we do not use the override keyword, it is new. We hope you have finally understood new and override.

a.cs
class zzz
{
public static void Main()
{
}
}
abstract class xxx
{
public abstract int i
{
get ;
}
}
class yyy : xxx
{
public override int i {
get
{
}
set
            {
            }
}
}

Compiler Error
a.cs(20,1): error CS0546: ‘yyy.i.set’: cannot override because ‘xxx.i’ does not have an overridable set accessor

In class xxx, the abstract property has only a get accessor. In the derived class we are implementing both the get and the set. The original never ever had a set. This is unacceptable to the compiler. Thus we have no choice but to implement only the accessors that are present in the original. A get accessor can be viewed as a method which returns a value but accepts no parameters.

a.cs
class yyy
{
public void i {
}
}

Compiler Error
a.cs(3,13): error CS0547: ‘i’ : property or indexer cannot have void type

It makes no sense for an accessor to have a void type as a variable cannot be of type void. Void literally means ‘I do not know the type’ or no type at all.

a.cs
class yyy
{
public int i {
            set
            {
            return 10;
            }
}
}

Compiler Error
a.cs(6,2): error CS0127: Since ‘yyy.i.set’ returns void, a return keyword must not be followed by an object expression

A set accessor can be viewed as function which returns void but accepts one parameter which stands for the value of the property. Thus a set cannot return a value. If we remove the 10, we will not get an error.

a.cs
class zzz
{
public static void Main()
{
}
public int i {
            set
            {
            value = 20;
            }
}
}

The reserved variable value in the set can be changed at will. Though, understanding why anyone would want to do such dumb stuff is beyond us.

a.cs
class zzz
{
public static void Main()
{
}
public int i {
            set
            {
            int value;
            }
}
}

Compiler Error
a.cs(9,6): error CS0136: A local variable named ‘value’ cannot be declared in this scope because it would give a different meaning to ‘value’, which is already used in a ‘parent or current’ scope to denote something else

We cannot however create a variable value as it will clash with the variable value which is already present by default in the set.

a.cs
class zzz
{
public static void Main()
{
yyy a = new yyy();
a.i = 10;
xxx b = new xxx();
((yyy)b).i = 20;
b.i = 10;
}
}
class yyy
{
public int i {
            set {
            }
}
}
class xxx : yyy
{
public int i {
                get {
                 return 10;
                 }
} }
Compiler Error
a.cs(9,1): error CS0200: Property or indexer ‘xxx.i’ cannot be assigned to — it is read only

In the class yyy, the property i has only the set accessor. In the class xxx which derives from yyy, we have implemented only the get accessor. The property i in class xxx hides the i of yyy. They do not add up. What we are trying to say is that both these properties are independent of each other. What we had thought C# would have done is, taken the set from one class and added it to the second. However, that does not make sense. It treats them independently. If we want to use the property of the class yyy, then we need to explicitly cast it as we have done for b. Thus the property i of class yyy gets hidden but can be accessed.

A property is not necessarily slower than a variable. A variable access normally initializes some memory, whereas a property executes a method. This is not necessarily slower as at times, C# will rewrite your property methods to memory accesses. This is called inlining of code. Except for minor differences, all that we mentioned about virtual, abstract and new apply also to a property. The difference is, if the original property has a get and a set, the derived class will only implement a set or a get.

Indexers

An indexer lets us access members of a class as if it were an array.

a.cs
public class zzz
{
public static void Main()
{
yyy a = new yyy();
a[1] = 10;
}
}
public class yyy {
}
Compiler Error
a.cs(6,1): error CS0021: Cannot apply indexing with [] to an expression of type ‘yyy’

We have created an object a that looks like yyy. The object a, in no sense of the word is an array. We are assuming that a is an array and we’ve used the array syntax a[], hence it gives us an error.

a.cs
public class zzz
{
public static void Main()
{
yyy a = new yyy();
a[1] = 10;
}
}
public class yyy
{
public int this[int i]
{
set {
      System.Console.WriteLine(“in get “ + value + “ “ + i);
}
}
}

Output
in get 10 1

We’ve added a few lines to have the array notation work with an object that looks like yyy. To implement indexers, we need to create a special property called this. This is a reserved word. As of now, we have a parameter i (an int) in the square brackets. When we did properties earlier,we learnt that a set gets called whenever we want to initialize or set a  variable. Within the set accessor we have a special variable called value which stores the value passed to the set, in this case 10. The variable i will hold the value 1 as the array parameter is 1.
This is how we implement arrays when there are none.

a.cs
public class zzz
{
public static void Main()
{
yyy a = new yyy();
System.Console.WriteLine(a[1]);
}
}
public class yyy
{
public int this[int i]
{
set
{
System.Console.WriteLine(“in get “ + value + “ “ + i);
}
get
{
System.Console.WriteLine(“in set “ + i);
return 23;
}
}
}

Output
in set 1
23

The rules binding properties are applicabe to indexers too. When you want to read the value of a[1], the get gets called. The major difference between properties and indexers is that when you implement the code for indexers you have to understand that the get and set get called with a variable which is the array parameter value. The code will have to understand array simulation.


a.cs
public class zzz
{
public static void Main()
{
yyy a = new yyy();
a[“hi”] = 30;
System.Console.WriteLine(a[“hi”]);
}
}
public class yyy
{
public int z;
public int this[string i]
{
set
{
System.Console.WriteLine(“in get “ + value + “ “ + i);
z = value;
}
get
{
System.Console.WriteLine(“in set “ + i);
return z;
}
}
}

Output
In get 30 hi
In set hi
30

The this property has a return value, in this case, an int. Also the [] brackets can contain data types other than an int. In this case a string. The string i  has a value hi as that is what we passed in the array brackets. You can have two this’s in your class. You have to decide what data type to use in the array brackets. An indexer is very useful when you have a database object and you want to access the data in the fields using a notation [“fieldname”]
Indexers follow the same concepts of virtual, new, override etc.

a.cs
class zzz
{
public static void Main()
{
yyy a = new yyy();
a[1] = 10;
a[“one”] = 10;
a[“hi”,2] = 30;
}
}
class yyy
{
public int this [ int i]
{
set
{
System.Console.WriteLine(“one int “+ i + “ “ + value);
}
}
public int this [ string i]
{
set
{
System.Console.WriteLine(“one string “+ i + “ “ + value);
}
}
public int this [ string i, int j]
{
set
{
System.Console.WriteLine(“one string and int “+ i + “ “ + j + “ “ + value);
}
}
}


Output
one int 1 10
one string one 10
one string and int hi 2 30

The signature of an indexer is the number and types of formal parameters. The return value and the names of the parameters do not contribute to the indexers signature. Thus we have overloaded the indexers to take an int, string or a string int combination. Each time a different function gets called. The point to understand is that all the indexers have to return the same data type, in our case int. The same rules that apply to function overloading apply here also. Functions cannot differ only by return values. We are sure that for indexers in the next version, C# should/must make an exception.

A property is identified by its name, an indexer by its signature. There is no concept of property overloading in C#.

a.cs
class zzz
{
public static void Main()
{
}
}
class yyy
{
public static int this [ int i]
{
set
{
}
}
}

Compiler Error
a.cs(9,19): error CS0106: The modifier ‘static’ is not valid for this item

A property can be both an instance member which is the default or static. An indexer unfortunately can only be an instance member and not static. God alone knows why this discrimination against indexers. Once again no rational reason for the above error. Obviously you cannot create a variable with the same name as that of the parameter passed in the indexer.

a.cs
class zzz
{
public static void Main()
{
xxx a = new xxx();
a[2] = 20;
System.Console.WriteLine(a[2]);
}
}
class yyy
{
public virtual int this [ int i]
{
get
{
System.Console.WriteLine(“yyy get “ + i);
return 20;
}
set
{
System.Console.WriteLine(“yyy set “ + value + “ “ + i);
}
}
}
class xxx : yyy
{
public override int this [ int i]
{
get
{
int p = base[i];
System.Console.WriteLine(“xxx get “ + i + “ “ + p);
return 200;
}
set
{
System.Console.WriteLine(“xxx set “ + value + “ “ + i);
base[i] = value;
}
}
}

Output
xxx set 20 2
yyy set 20 2
yyy get 2
xxx get 2 20
200

The above example deals with calling the indexers of the base class. At times when we are overriding code in the derived class, we would like to call the original indexer in the base class first. The first rule that we have to adhere to is that the indexer in the base class must be declared virtual. In the derived class, we are now declaring it with the modifier override. Same rules as above. In the set accessor, we have to call the original as base[i], where i is the index to the indexer. Also we need to pass it the value to initialize itself. This is stored in the variable value. This a[2] in Main gets replaced by base[2] in the set. In get the reverse takes place. Here we need to place base[i] on the right of the equalto sign, the original get will return a value, in this case 20, which we are storing in a variable p. What we do with p as well as the value from the get is our business.

a.cs
class yyy
{
public int this [ byte i , string j]
{
get
{
return 10;
}
set
{
}
}
int get_Item(byte i,string j)
{
return 20
}
void set_Item(byte i,string j , int value)
{
}
}

Compiler Error
a.cs(5,1): error CS0111: Class ‘yyy’ already defines a member called ‘get_Item’ with the same parameter types
a.cs(9,1): error CS0111: Class ‘yyy’ already defines a member called ‘set_Item’ with the same parameter types

Like a property, an indexer also gets a name change. If people can get their bodies pierced then why cannot a indexer get converted to a series of functions starting with get? For a get, the parameters are the same as we pass to an indexer. It has a return value and the type of the indexer. Also the set has one more added parameter and that is the free variable value.

No comments:

Post a Comment

No String Argument Constructor/Factory Method to Deserialize From String Value

  In this short article, we will cover in-depth the   JsonMappingException: no String-argument constructor/factory method to deserialize fro...