In the C#TM language, all classes and structs inherit from object, which is an alias of System.Object. For fields and methods declared within a class, there are five possible types see the Five Protections below. Since a struct cannot have descendants, it cannot use the protected permission for its members. These are public, protected internal, protected, internal, and private. Note that internal is default protection. The most open protection is public, while the most closed protection is private.
Methods in base classes may be overridden in subclasses only if they have virtual or override denoting them. If a method has override denoting it, that means that means a base class method of the same name in the inheritance chain has virtual denoting it in its first implementation, though any number of subclasses in the chain going down can override that implementation. When a classes methods are all either virtual or overridden, they behave just like JavaTM methods.
However, C# methods without virtual or override keywords, are very different to java. When you try to implement such a method in a subclass, you are required to use the new keyword method modifier, and as such, you effectively hide the implementation of methods depending on what type an object is. In other words, when a method is hidden, what method is called depends on what the type that object is currently cast as. So if you cast a C# object to another type and call the same method, you cannot know whether you really are calling the same method, unless you know your methods are virtual or override.
However, even if you do know your method in a top level in the inheritance is virtual, while the same method on the bottom of the inheritance is override, you still cannot be sure that the same method will be called on the same object cast between those two types unless you know that nowhere in the inheritance chain between those two classes is that method hidden with sealed and reimplemented with new.
An interface can be implemented by both structs and classes. Essentially, an interface is a set of abstract members. That is, they do not have implementations, but define what all their implementing instances must have. Hence, if you have an Object that is an instance of the interface IDisposable, you know that it implements the Dispose method, which is specified in the IDisposable interface. Hence, if you have any object that implements IDisposable, the correct way to call that method would be to cast it to IDisposable, either with a cast or using the keyword as, and then call Dispose. Don't do it as in Java where you would simply call the Dispose method on that object without casting, because, as detailed above, you cannot be sure whether that object is hiding the Dispose method with a new method of the same name not intended for overriding. You don't have to worry about hiding methods in Java, where all methods are virtual. But in C#, things are more complicated, and you need to be more careful with more powerful features of more powerful languages.
Methods can also be overloaded, which is distinct from overriding. Overloading means the same method name is used, but the parameters are different. This enables you to make custom method implementations that do the same task differently for different types of parameters, and yet give it the same method name. This is possible in C++, but not in C, where function names rapidly become cryptic in order to solve such problems. Another feature of C# is the ability to have methods with variable numbers of arguments using the (params) keyword.
Whatever class or interface a class extends or implements, that child class is an Object of those parent types and can be converted to those types with what is called a cast. A cast consists of the type between parentheses. Example:
public Object GetObj(){ String S = "hi"; return (Object) S; } //This is answered by: String S2 = (String) GetObj(); //or else by using as: String S3 = GetObj() as String;
With the first way above, by directly casting, it will throw a System.InvalidCastException if it fails. However, by using the second way above, with the as keyword, no Exception will be thrown. Instead, if it fails, the result will be null. So you would need to check for null before using that variable unless you were sure it would not fail.
An Object in memory remains all its types until it is set to null and garbage collected. But to assign it to a type or to use it of some type, you need to cast it the specific type. Note: an Object can only be casted to the following types: the original type it was instantiated as using the new operator, all the super classes of that class, and all the interfaces of that class, and all the interfaces of its super classes.
All classes have only one direct parent class, called either base or super, it refers to the same concept. If that parent class is Object, then it is implied and does not need to be specifically extended in the code. Unlike in Java, however, there is no clear distinction of whether the first listed base is the base class or an interface, since if it extends from object, it does not have to be listed, for as you can see in the following code, the base class, if not object, and all the interfaces, are listed in separated in a comma delimited list:
public MyClassWithDeeperAncestry : MySuperClass, IMyFirstInterface, IMySecondInterface { ... } public MyClassExtendingObject : IMyFirstInterface, IMySecondInterface { ... }
In the above example, it is not absolutely clear looking at the code whether MySuperClass is an interface the base class of MyClassWithDeeperAncestry. Likewise, it is not clear whether MyFirstInterface is an interface or the base class of MyClassExtendingObject. To make this clear, the convention is to use capital I at the beginning of all Interface names, followed by another capital letter for the first word of the actual interface name.
Actually, I find this as one of the main shortcomings of the C# language when compared to Java. I would have preferred that the keywords extends and implements were included in C#, or some other way that would have eliminated this ambiguity. However, the die has been cast and it cannot be changed.
The Five Possible Protections:
public | Used to apply to members of classes and structs to make them accessible to all other classes and structs, whether in the same assembly or outside. This is the most open level of permissions. |
protected | Used to apply to members to limit their scope to itself and to descendants regardless of whether the descendant is within the same assembly. |
internal | If none of the three protection keywords are there (public, protected, or private), it defaults to internal protection. Members that are under internal protection can only be accessed by other classes and structs within the same assembly. Descendants outside that assembly cannot access such members. |
protected internal | This is the combination of protected and internal permissions. Members that are under protected internal protection can be accessed by other classes and structs within the same assembly as well as by descendants outside that assembly. |
private | Members marked private can only be accessed only from within that class or struct. They cannot be accessed outside that class or struct. This is the most closed level of permissions. However, one object of such a class or struct can access private Members of another object of the same class or struct. |
*OracleTM and JavaTM are registered trademarks of Oracle and or its affiliates. Other names may be trademarks of their respective owners.*