120 likes | 163 Views
Effective C#, Chapter 1: C# Language Elements. Last Updated: Fall 2011. Agenda. Material From Bill Wagner Effective C#: 50 Specific Ways to Improve Your C# Cover Items 6 and 9 Goal: Compare/Contrast with Java. Item 6: Distinguish Between Value Types and Reference Types.
E N D
Effective C#, Chapter 1: C# Language Elements Last Updated: Fall 2011
Agenda • Material From Bill Wagner • Effective C#: 50 Specific Ways to Improve Your C# • Cover Items 6 and 9 • Goal: Compare/Contrast with Java
Item 6: Distinguish Between Value Types and Reference Types • Structs or Classes? • Impacts both correctness and performance • C++ Objects use Struct model • Plus pointers… • Java Objects use Class model • C# Wants to have it both ways • Question: Is this a good idea?
Objections to Reference Types • Setters and getters need copies • Storing or returning references breaks encapsulation • C# value types make copies by default • 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# 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);
Item 9: Understand Relationship Among the Many Equals • C# Defines 4 different equality tests • You can redefine any of them • But you shouldn’t • Complexity is due to value type/reference type distinction • Interesting bottom line in case where C# and Java overlap • Wagner and Bloch disagree! • We should understand why
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 );
First, the Easy Cases • RedefiningReferenceEquals is like redefining Java’s “==“ operator • 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
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? • Wagner’s recipe violates this property
Wagner’s Recipe public override bool Equals( object right ) { // in class MyType // check null if (right == null) return false; // 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 ); }
Why Does Wagner Use Exact Type Matches? • Remember the result in Bloch: • Not possible to extend an instantiable class, add abstract state, and satisfy symmetry, transitivity, and substitution principles. • Bloch’s approach: • Favor composition over inheritance • Save inheritance for interfaces • Wagner’s approach • Sacrifice substitution principle
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)