Effective C#, Chapter 1: C# Language Elements Last Updated: Fall 2011 Agenda Material From Bill Wagner Cover Items 6 and 9 2 Effective C#: 50 Specific Ways to Improve Your C# Goal: Compare/Contrast with Java C# Language Elements Item 6: Distinguish Between Value Types and Reference Types Structs or Classes? Impacts both correctness and performance C++ Objects use Struct model Java Objects use Class model C# Wants to have it both ways 3 Plus pointers… Question: Is this a good idea? C# Language Elements Objections to Reference Types Setters and getters need copies C# value types make copies by default 4 Storing or returning references breaks encapsulation But value types are not polymorphic! C# requires you to decide, upfront, between value and reference type Note Bloch’s Java solution Favor Immutability C# Language Elements C# Structs vs. Classes // C# Value Type // No polymorphic behavior public struct Employee { private string _name; private decimal _salary; public void Pay (BankAccount b) { b.Balance += _salary }; } // C# Reference Type // Polymorphic behavior public class Employee { private string _name; private decimal _salary; public void Pay (BankAccount b) { b.Balance += _salary }; } // Client code – Note Difference of struct vs. class Employee e1 = Employees.Find(“CEO”); e1.Salary += Bonus; // Is bonus permanent addition to salary? e1.Pay(CEOBankAccount); 5 C# Language Elements Item 9: Understand Relationship Among the Many Equals C# Defines 4 different equality tests Complexity is due to value type/reference type distinction Interesting bottom line in case where C# and Java overlap 6 You can redefine any of them But you shouldn’t Wagner and Bloch disagree! We should understand why C# Language Elements Four Ways To Test Equality // Object Identity – Never Redefine public static bool ReferenceEquals ( object left, object right ); // Implements Equals() as Dynamic Binding – Never Redefine public static bool Equals ( object left, object right ); // Like Java equals() for C# reference types – Redefine as needed public virtual bool Equals( object right); // Equality for value types // Nothing similar in Java // Goal of redefining is simply to improve performance public static bool operator==( MyClass left, MyClass right ); 7 C# Language Elements First, the Easy Cases Redefining ReferenceEquals is like redefining Java’s “==“ operator 8 Doesn’t make sense to redefine static Equals is for dynamic binding Effect is to invoke nonstatic Equals on left hand argument Doesn’t make sense to redefine C# Language Elements Interesting Case: virtual Equals() Same situation as Java’s Equals() method in the Object class Same set of constraint Reflexivity Symmetry Transitivity Liskov Substitution Principle? 9 Wagner’s recipe violates this property C# Language Elements Wagner’s Recipe public override bool Equals( object right ) { // check null if (right == null) return false; // in class MyType // Optimization for comparison to self if (object.ReferenceEquals( this, right )) return true; // Type check that is NOT Bloch’s recipe if (this.GetType() != right.GetType()) return false; // Alternative equivalent to Bloch’s recipe // MyType rightAsMyType = right as MyType; // if (rightAsMyType == null) return false; // Compare this type's contents here // This part is equivalent to Bloch’s recipe return CompareFooMembers( this, right as Foo ); } 10 C# Language Elements Why Does Wagner Use Exact Type Matches? Remember the result in Bloch: Bloch’s approach: 11 Not possible to extend an instantiable class, add abstract state, and satisfy symmetry, transitivity, and substitution principles. Favor composition over inheritance Save inheritance for interfaces Wagner’s approach Sacrifice substitution principle C# Language Elements Problem with Wagner’s Recipe public static bool myCheck (List<Points> points) { Point p = ... // a Point with value (1,2) return points.Contains(p) } // // // // Suppose the list contains subclasses of Point WITHOUT client visible state. Then the return value should be true if a subclass of Point with state (1,2) is in the list. Reason: This is simply the Liskov Substitution Principle. // // // // Of course, if the list contains subclasses WITH client visible state, then the return value CANNOT be true if Point(1,2,x) is in the list. Reason: Otherwise guaranteed a Symmetry or Transitivity failure. // See Bloch, page 39 for more details of this example (in Java) 12 C# Language Elements