Do you know, that keyword 'new' can be used in a definition of class members? I knew about it, but have never used it for many years I work in the software industry. But recently I have found a couple of scenarios, where this opportunity is very useful. Here I want to share my findings.
In class member definition keyword 'new' works similar to overloading of methods. But it supports scenarios, which are not allowed for overloading. For example, you can make read-only property writable. You are not allowed to do it using overload or override:
public abstract class Parent
{
public virtual int Value
{
get { return 0; }
}
}
public class Child : Parent
{
public override int Value
{
get;
set; // Compilation error here
}
}
But you can do it using 'new' keyword:
public abstract class Parent
{
public int Value
{
get { return 0; }
}
}
public class Child : Parent
{
public new int Value
{
get;
set;
}
}
Also, you can't change only the type of method result using overload or override:
public abstract class Parent
{
public virtual int GetValue()
{
return 0;
}
}
public class Child : Parent
{
public override string GetValue() // Compilation error
{
return string.Empty;
}
}
But you also can do it with 'new' keyword:
Nevertheless, there are some things, you should know to use this possibility correctly. If you take a look at Avivo recommendations for writing code, you can find the following one:
Even in this case, I want to show you a couple of examples, demonstrating, how your program can benefit from the 'new' keyword.
What is a problem with this design? The problem is that you still can write the following code without compilation errors:
Certainly, you'll get your error at runtime, but you can do better with the 'new' keyword. Take a look at the following code:
Now the previous code will throw a compilation error. You are not allowed to set priority for system items.
So, are we done? Not yet. What do you think the following code will print:
If you think it is 0, you are right. 'New' keyword does not work like overriding. After casting to the base class, code of old property will be called.
Can we somehow overcome this limitation? Yes, we can:
As you can see, in both base and derived classes we use a common protected field for work of Priority property. It allows us to get the correct value of this property even after casting of a CustomItem object to the base Item class.
There is one more thing you should know here. We have made read-only property writable. Can we do writable property read-only? At first glance it looks like we can:
The main problem with this design, from my point of view, is that we still can change the priority of SystemItem objects:
This code will print 10, although system items must always have priority of 0.
This is the reason, why I think you should not try to make writable properties read-only.
Let's consider another example. In our program, we want to have several different storages of items. Classes of these storages must have method GetItems, which returns a collection of Item objects.
public abstract class Parent
{
public int GetValue()
{
return 0;
}
}
public class Child : Parent
{
public new string GetValue()
{
return string.Empty;
}
}
Nevertheless, there are some things, you should know to use this possibility correctly. If you take a look at Avivo recommendations for writing code, you can find the following one:
Don't hide inherited members with new keyword
Even in this case, I want to show you a couple of examples, demonstrating, how your program can benefit from the 'new' keyword.
Making read-only property writable
Let's consider the following example. In your program, you model some items (tasks, posts, etc.). Each item has it's priority (here we'll model it as integer number). There are two types of items: system and custom. System items always must have priority of zero. For custom items, a user must have the ability to change the priority. How will we model classes of items? One approach is the following:public abstract class Item
{
public virtual int Priority { get; set; }
}
public class CustomItem : Item { }
public class SystemItem : Item
{
public override int Priority
{
get { return 0; }
set { throw new InvalidOperationException(); }
}
}
What is a problem with this design? The problem is that you still can write the following code without compilation errors:
SystemItem item = new SystemItem();
item.Priority = 10;
Certainly, you'll get your error at runtime, but you can do better with the 'new' keyword. Take a look at the following code:
public abstract class Item
{
public int Priority
{
get { return 0; }
}
}
public class CustomItem : Item
{
public new int Priority { get; set; }
}
public class SystemItem : Item { }
Now the previous code will throw a compilation error. You are not allowed to set priority for system items.
So, are we done? Not yet. What do you think the following code will print:
CustomItem customItem = new CustomItem();
customItem.Priority = 10;
Item item = customItem;
Console.WriteLine(item.Priority);
If you think it is 0, you are right. 'New' keyword does not work like overriding. After casting to the base class, code of old property will be called.
Can we somehow overcome this limitation? Yes, we can:
public abstract class Item
{
protected int _priority = 0;
public int Priority
{
get { return _priority; }
}
}
public class CustomItem : Item
{
public new int Priority
{
get { return _priority; }
set { _priority = value; }
}
}
public class SystemItem : Item { }
As you can see, in both base and derived classes we use a common protected field for work of Priority property. It allows us to get the correct value of this property even after casting of a CustomItem object to the base Item class.
There is one more thing you should know here. We have made read-only property writable. Can we do writable property read-only? At first glance it looks like we can:
public abstract class Item
{
protected int _priority = 0;
public int Priority
{
get { return _priority; }
set { _priority = value; }
}
}
public class CustomItem : Item
{
}
public class SystemItem : Item
{
public new int Priority
{
get { return _priority; }
}
}
The main problem with this design, from my point of view, is that we still can change the priority of SystemItem objects:
SystemItem systemItem = new SystemItem();
Item item = systemItem;
item.Priority = 10;
Console.WriteLine(systemItem.Priority);
This code will print 10, although system items must always have priority of 0.
This is the reason, why I think you should not try to make writable properties read-only.
Changing the result type of a method
Let's consider another example. In our program, we want to have several different storages of items. Classes of these storages must have method GetItems, which returns a collection of Item objects.
public abstract class Storage
{
public IEnumerable<Item> GetItems()
{
return GetItemsFromDatabase();
}
private IEnumerable<Item> GetItemsFromDatabase()
{
...
}
}
Among all storages we also have CustomStorage, that can contain only instances of CustomItem objects. For this storage, we would like to have GetItems method returning a collection of CustomItem objects. We can do it using 'new' keyword:
public abstract class Storage
{
public IEnumerable<Item> GetItems()
{
return GetItemsFromDatabase();
}
private IEnumerable<Item> GetItemsFromDatabase()
{
...
}
}
public class CommonStorage : Storage
{}
public class CustomStorage : Storage
{
public new IEnumerable<CustomItem> GetItems()
{
return GetCustomItemsFromDatabase();
}
private IEnumerable<CustomItem> GetCustomItemsFromDatabase()
{
...
}
}
The problem with this code is the same as in the previous case. After casting object of CustomStorage type to Storage type we'll get the implementation of GetItems from Storage class, which will lead to call of GetItemsFromDatabase method instead of GetCustomItemsFromDatabase. We can fix this problem using the same technic, as in the previous case, but here we'll use a protected field of delegate type:
public abstract class Storage
{
protected Func<IEnumerable<Item>> _getItems;
protected Storage()
{
_getItems = GetItemsFromDatabase;
}
public IEnumerable<Item> GetItems()
{
return _getItems();
}
private IEnumerable<Item> GetItemsFromDatabase()
{
...
}
}
public class CommonStorage : Storage
{}
public class CustomStorage : Storage
{
public CustomStorage()
{
_getItems = GetCustomItemsFromDatabase;
}
public new IEnumerable<CustomItem> GetItems()
{
return GetCustomItemsFromDatabase();
}
private IEnumerable<CustomItem> GetCustomItemsFromDatabase()
{
...
}
}
Violation of Liskov substitution principle and other problems
Initially, this section was not in the article. But I have got a lot of comments, stating that usage of 'new' keyword is a violation of Liskov substitution principle, that it just hides existing class members, that there are approaches to solve the same problem another way. I feel, that I must express my opinion about these issues.
Let me start from Liskov substitution principle. Here is what Wikipedia says about it:
If S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program
or (from here):
If S is a declared subtype of T, objects of type S should behave as objects of type T are expected to behave, if they are treated as objects of type T
You can read here, how 'new' keyword can violate Liskov substitution principle.
I agree, that classes in my Parent/Child examples violate this principle, but I don't agree that classes from Item example and from Storage example violate it (I'm talking about final versions of these classes, of cause). This is exactly why these protected fields in the implementations were introduced. They allow us to adhere to this principle.
In other words, I don't believe, that usage of 'new' keyword automatically means a violation of Liskov substitution principle. I think, that it can lead to the violation, and we must understand it and make certain steps to avoid it. In my article, I try to suggest one way of doing it.
I absolutely agree, that 'new' keyword hides existing methods of a base class. But sometimes this is what we need.
Here we come to another point. As it was justly mentioned by readers, sometimes there are different ways to solve the same problem. For example, our problem with storage can be solved using generics like this:
public abstract class GenericStorage<T>
where T : Item
{
public abstract IEnumerable<T> GetItems();
}
public class CommonGenericStorage : GenericStorage<Item>
{
public override IEnumerable<Item> GetItems()
{
return GetItemsFromDatabase();
}
private IEnumerable<Item> GetItemsFromDatabase()
{
...
}
}
public class CustomGenericStorage : GenericStorage<CustomItem>
{
public override IEnumerable<CustomItem> GetItems()
{
return GetCustomItemsFromDatabase();
}
private IEnumerable<CustomItem> GetCustomItemsFromDatabase()
{
...
}
}
But here we can face other problems. Consider, that we want to write some code, which should work with any our generic repository. I can expect, that method GetItems will always return a collection, where each element is assignable to Item class. How to write code, that can work with any storage in the same way? For example, I want to have a list of all my storages. Unfortunately, the following code does not work:
var storages = new List<GenericStorage<Item>>
{
new CommonGenericStorage(),
(GenericStorage<Item>)new CustomGenericStorage() // Compilation error
};
In this particular case, we can solve the problem by extracting covariant interface:
public interface IGenericStorage<out T>
where T : Item
{
IEnumerable<T> GetItems();
}
public abstract class GenericStorage<T> : IGenericStorage<T>
where T : Item
{
public abstract IEnumerable<T> GetItems();
}
...
Now we can create our list of storages:
var storages = new List<IGenericStorage<Item>>
{
new CommonGenericStorage(),
new CustomGenericStorage()
};
But it is not always possible to make interface covariant.
It was my view on problems, related to the usage of 'new' keyword. I want to thank John Brett and other members of CodeProject community for their comments and discussion of the topic.
Conclusion
I hope, I managed to demonstrate, that in some cases usage of 'new' keyword in definitions of class members can be very useful and make the interface of your classes more readable and reliable. I don't believe, that usage of 'new' keyword automatically leads to problems, but we should be aware of ways to overcome them. In any case, it is up to you to decide, which approach to use in your design.
You can read more my articles on my blog.
No comments:
Post a Comment