Effective C#
-
Upload
lantoli -
Category
Technology
-
view
530 -
download
1
Transcript of Effective C#
#dotNETSpain2015
Leo Antoli @lantoliEffective C#
.NET Conference 2015
Y
AX B
#dotNETSpain2015
#dotNETSpain2015
WARNING:Most of the code in these slides is about how NOT to do it
#dotNETSpain2015
In order to master a language...
Wife mine this night
building an unusual
dinner at my home,
invited you are.
#dotNETSpain2015
Let's get started...
#dotNETSpain2015
string and String
Any difference ?
string a = "hello";
String b = "Hello";
#dotNETSpain2015
string and String
NO DIFFERENCE but prefer string
string -> C# alias for System.String
String -> System.String is a CLR type
#dotNETSpain2015
StringBuilder
string[] listStr = { "how ", "are", "you"};string res = "";foreach (string str in listStr) {
res += str;}
#dotNETSpain2015
StringBuilder
string str = "a" + "b" + "c" + "d" + "e" + "f";
string str = new StringBuilder().
Append("a").Append("b").Append("c").
Append("d").Append("e").Append("f").ToString()
#dotNETSpain2015
Compiler makes some initial work with constants
string str = "a" + "b" + "c" + "d" + "e" + "f";
Almost 10 times faster !!!
Slower:
string str = new StringBuilder().
Append("a").Append("b").Append("c").
Append("d").Append("e").Append("f").
ToString()
#dotNETSpain2015
Strong-typed Language
Any difference ?
int v = 10;string res = "hello";foreach (string str in listStr) { ... }
var v = 10;var res = "hello";foreach (var str in listStr) { ... }
#dotNETSpain2015
Overloading and Overriding
Parent p = new Parent(); Child c = new Child();
Parent c2 = new Child();
call(p);
call(c);
call(c2);
public class Child : Parent ...
public class Utilities {
void call(Parent o) { Console.WriteLine("Parent called"); }
void call(Child o) { Console.WriteLine("Child called"); }
#dotNETSpain2015
Overloading and Overriding
Polymorphism types:Overloading (params) - compilation time - decide method to call on input paramsOverriding (inheritance) - execution - decide method to call on instance class
Parent called
Child called
Parent called
#dotNETSpain2015
Overloading and Overriding
public Point(int x, int y) {...
public bool Equals(Point p) {
return x == p.x && y == p.y;
}
... GetHashCode ...}Point p1 = new Point(10,20);
Point p2 = new Point(10,20);
Console.WriteLine(p1.Equals(p2)); ???
HashSet<Point> set = new HashSet<Point>();
set.add(p1);
Console.WriteLine(set.Contains(p2)); ???
#dotNETSpain2015
Overloading and Overriding
true
false
public bool Equals(Point p)
public override bool Equals(object obj)
#dotNETSpain2015
String equality
string s1 = "hello";
string s2 = "HELLO".ToLower();
object o1 = s1;
object o2 = s2;
public void eq(bool expr) { Console.WriteLine(expr) };
eq( s1 == s2); // ???
eq( o1 == o2 ); // ???
eq( s1 == o2 ); // ???
eq( s1.Equals(s2) ); // ???
eq( s1.Equals(o2) ); // ???
eq( o1.Equals(s2) ); // ???
#dotNETSpain2015
String equality
string s1 = "hello";
string s2 = "HELLO".ToLower();
object o1 = s1;
object o2 = s2;
public void eq(bool expr) { Console.WriteLine(expr) };
eq( s1 == s2 ); // True
eq( o1 == o2 ); // False
eq( s1 == o2 ); // False
eq( s1.Equals(s2) ); // True
eq( s1.Equals(o2) ); // True
eq( o1.Equals(s2) ); // True
#dotNETSpain2015
String equality
Operators are static methods.Static methods can be overloaded but not overridden (can't be inherited).So decision is in compilation time.
public static bool == (String left, String right)
#dotNETSpain2015
Inheritance override
interface Inter { void Message(); }
class A : Inter {
public void Message() { Console.WriteLine("A"); }}
class B : A {
public new void Message() { Console.WriteLine("B"); }}
B b = new B();
b.Message(); // ???
((Inter)b).Message(); // ???
((A)b).Message(); // ???
A a = b;
a.Message(); // ???
((Inter)a).Message(); // ???
#dotNETSpain2015
Inheritance override
VIRTUAL, OVERRIDE, SEALED, NEW
B b = new B();
b.Message(); // B
((Inter)b).Message(); // A
((A)b).Message(); // A
A a = b;
a.Message(); // A
((Inter)a).Message(); // A
TRY TO AVOID:
- Different behavior depending on the reference type
- Avoid "new" to redefine sealed methods
- Avoid explicit interface implementation
#dotNETSpain2015
Inheritance override
Design for inheritance or else prohibit it
public class Inheritable {
virtual public void Message() { ... }
...
sealed public class DontInherit {
public new void Message() { ... }
...
#dotNETSpain2015
Test data in a fast way
string[] strs = {"hello", "how", "are", "you"};
int[] ints = {10, 20, 30};
Point[] points = { new Point(10, 20), new Point(20, 40)};
List<string> list = new List<string>();
list.Add("hello");
list.Add("how");
Dictionary<string,int> dict = new Dictionary<string,int>();
dict.Add("hello", 13);
dict.Add("how", 15);
#dotNETSpain2015
Test data in a fast way
string[] strs = {"hello", "how", "are", "you"};
int[] ints = {10, 20, 30};
Point[] points = { new Point(10, 20), new Point(20, 40)};
If Enumerable and Add method:
var list = new List<string> {"hello", "how"};
var dict = new Dictionary<string,int> {
{ "hello", 13 },
{ "how", 15 },
};
#dotNETSpain2015
CLR & C#
+ ++ +C
#dotNETSpain2015
CLR & C# (assembly compatibility)
#dotNETSpain2015
CLR & C# (properties vs fields)
FIELD:
public String Description;
PROPERTY:
private String comment;
public String Comment
{
get { return comment; }
set { comment = value; }
}
#dotNETSpain2015
CLR & C# (properties vs fields)
FIELD: Binary incompatible if later changed to property
public String Description;
public String Description { get; set; }
Implementation changes keep binary compatibility
Never user public fields
#dotNETSpain2015
CLR & C# (readonly vs const)
public const int DefaultPageSize = 10;
public static int DefaultPageSize2 { get { return 10; } }
public static readonly int DefaultPageSize3 = 10;
#dotNETSpain2015
CLR & C# (readonly vs const)
public const int DefaultPageSize = 10;
public static int DefaultPageSize2 { get { return 10; } }
public static readonly int DefaultPageSize3 = 10;
Const -> compilation-time (If assembly with const changes, all consumers
must recompile, const is not supported in CLR)
Only constant values, only simple types, ...
Readonly -> runtime
Never user public const if value could change in the future
#dotNETSpain2015
CLR & C# (readonly)
Can be set in constructor, can't be set after object is created
private readonly int _elementCount;
public int ElementCount { get { return _elementCount; } }
MyClass(int pages, int pageSize)
{
_elementCount = pages * pageSize;
}
THIS CAN BE CHANGED:
public int ElementCount2 { get; private set; }
#dotNETSpain2015
Any difference ?
public void Method1(String param1 = "hello", int param2 = 10)
{
// DO STUFF HERE
}
public void Method2() {Method2("hello");}
public void Method2(String param1) { Method2(param1, 10);}
public void Method2(String param1, int param2)
{
// DO STUFF HERE
}
CLR & C# (optional params)
#dotNETSpain2015
Default param values (and named params) are in callsite assembly CIL. If default values (or names) change in calling site, all callsite assemblies must be recompiled
public void Method1(String param1 = "BYE", int param2 = 10)
{
// DO STUFF HERE
}
CLR & C# (optional params)
#dotNETSpain2015
Pros and cons ?
Point p = new Point(x: 10, y: 20);
Point p = new Point { X=10, Y=20 };
CLR & C# (optional params vs object initializers)
#dotNETSpain2015
Pros and cons ?
Point p = new Point(x: 10, y: 20);EQUIVALENT TO:Point p = new Point(10, 20);
Point p = new Point { X=10, Y=20 };EQUIVALENT TO:Point p = new Point();p.X = 10;p.Y = 20;
CLR & C# (optional params vs object initializers)
#dotNETSpain2015
static class StringMethods {
public static bool IsLengthEven(this String str) {
return (str.Length & 1 ) == 1;
}
}
Console.WriteLine("is even hello: " + "hello".IsLengthEven());
Console.WriteLine("is even hello!: " + "hello!".IsLengthEven());
CLR & C# (extension methods)
#dotNETSpain2015
Value and Reference Types
#dotNETSpain2015
Value and Reference types
C# is not a PURE OO language, not everything is an object.
Heap / Stack
Reference: class, interface, delegate, array
Value: Predefined types (except string, object), struct, enum
#dotNETSpain2015
Enum
public enum Quarter
{
Q1 = 1,
Q2 = 2,
Q3 = 3,
Q4 = 4
};
Quarter q1 = Quarter.Q1;
Quarter qa;
Quarter qb = new Quarter();
Console.WriteLine("q1: " + q1); // ???
Console.WriteLine("qa: " + qa); // ???
Console.WriteLine("qb: " + qb); // ???
#dotNETSpain2015
Enum
public enum Quarter
{
Q1 = 1, Q2 = 2, Q3 = 3, Q4 = 4
};
Quarter q1 = Quarter.Q1;
Quarter qa;
Quarter qb = new Quarter();
Console.WriteLine("q1: " + q1); // Q1
Console.WriteLine("qa: " + qa); // Compilation error because local var,
it would work in fields.
Console.WriteLine("qb: " + qb); // 0 Value types initialized to 0 or
default value
#dotNETSpain2015
Struct arrays
PointC[] c = new PointC[1000]; // Any diference ???
for (int i = 0; i < c.Length; i++)
{
c[i] = new PointC {X = i};
}
PointS[] s = new PointS[1000]; // Any diference ???
for (int i = 0; i < s.Length; i++)
{
s[i] = new PointS { X = i };
}
#dotNETSpain2015
Struct arrays
PointC[] c = new PointC[1000];
// HERE 1000 NULL REFERENCES ARE CREATED
for (int i = 0; i < c.Length; i++) {
c[i] = new PointC {X = i};
}
PointS[] s = new PointS[1000];
// HERE 1000 STRUTS WITH X,Y=0 ARE CREATED
for (int i = 0; i < s.Length; i++) {
s[i] = new PointS { X = i };
}
#dotNETSpain2015
Struct constructor
struct NegativePoint {
public NegativePoint(int x, int y) : this() { X = x; Y = y; }
private int x; private int y;
public int X {
set
{
if (value >= 0) throw
new ArgumentOutOfRangeException("X must be negative");
x = value;
}
get { return x; }
...
NegativePoint pa = new NegativePoint(0, 0); /// ???
NegativePoint pb = new NegativePoint(-5,-10); /// Can we enforce negative X,Y
?
#dotNETSpain2015
Struct constructor
Default public constructor always exists in structs
NegativePoint pa = new NegativePoint(); /// Create invalid point 0,0
NegativePoint pa = new NegativePoint(0, 0); /// throws exception
NegativePoint pb = new NegativePoint(-5,-10); /// OK
#dotNETSpain2015
Structs & Value Types
- Normally used for performance issues (be sure you get it)- Non-nullable (uninitialized struct has default values)- No references (e.g. copy when passed as parameters)- So two references can not point to the same struct (pointA = pointB makes a copy), except ref params- Sealed (no inheritance allowed)- Default public constructor always exists (can not enforce valid state)- Always implement Equals to avoid reflection
#dotNETSpain2015
Reference Parameters (ref, out)
void Increment(int number) { number++; }
void IncrementRef(ref int number) { number++; }
int a = 10;
int b = 10;
Increment(a);
IncrementRef(ref b);
Console.WriteLine("A: " + a); /// ???
Console.WriteLine("B: " + b); /// ???
#dotNETSpain2015
Reference Parameters (ref, out)
Ref is as an alias or reference, it's an in/out param
Console.WriteLine("A: " + a); /// 10
Console.WriteLine("B: " + b); /// 11
#dotNETSpain2015
Reference Parameters (ref, out)
void Inc1(Point p) { p.X++; }
void Inc2(Point p) {
p = new Point { X = p.X + 1, Y = p.Y };
}
void IncRef(ref Point p) {
p = new Point { X = p.X + 1, Y = p.Y };
}
Point pa = new Point {X = 1, Y = 1}; Inc1(pa);
Point pb = new Point { X = 1, Y = 1 }; Inc2(pb);
Point pc = new Point { X = 1, Y = 1 }; IncRef(ref pc);
/// pa.X, pb.X, pc.X ???
#dotNETSpain2015
Reference Parameters (ref, out)
void Inc1(Point p) { p.X++; }
void Inc2(Point p) {
p = new Point { X = p.X + 1, Y = p.Y };
}
void IncRef(ref Point p) {
p = new Point { X = p.X + 1, Y = p.Y };
}
/// pa.X = 2
/// pb.X = 1
/// pc.X = 2 (pc is referencing a new object)
#dotNETSpain2015
Generics
#dotNETSpain2015
Generic classes
public class MyList<T> {
public void Add(T elem)
{
...
}
...
}
MyList<int> listInts = new MyList<int>();
MyList<Car> listCars = new MyList<Car>();
MyList<Vehicle> listVehicles = new MyList<Vehicle>();
MyList<Vehicle> list = new MyList<Car> (); /// Are generics covariant
???
Vehicle[] arr = new Car[10]; /// Are arrays covariant ???
#dotNETSpain2015
Generic classes
Generics are invariant, arrays are covariant.
MyList<Vehicle> = new MyList<Car> (); /// Compile error
Vehicle[] arr = new Car[10]; /// OK, maybe runtime exceptions
string[] strarray = {"hello", "bye"};
object[] objarray = strarray;
objarray[0] = 123; /// ArrayTypeMismatchException
Vehicle[] vs = new Car[10];
vs[0] = new Motorbike(); /// ArrayTypeMismatchException
#dotNETSpain2015
Constraints on Generics
Assignments ?
T elm = null;
T elm = new T();
Common ancestor ?
T elm;
elm.X = 10;
#dotNETSpain2015
Constraints on Generics
class MyClass<T> where T:
ClassOrInterface, // base class or interface
class, // any reference type
struct, // any value type
new() // with default public constructor
T elm = default(T); // null or 0-based value
#dotNETSpain2015
Variance in generic delegates
public delegate T Factory<T>();
void FillGarage(int numVehicles, Factory<Vehicle> factory) { /* ... */ }
Factory<Car> carFactory = () => new Car();
Factory<Vehicle> vehicleFactory = carFactory; // compile error
FillGarage(10, carFactory); // compile error
public delegate bool Predicate<T>(T elm);
void SendCarsToRepair(Car c, Predicate<Car> pred) {
if (pred(c)) {
...
Predicate<Vehicle> predFord = elm => elm.Make == "Ford";
SendCarsToRepair(myCar, predFord); // compile error
#dotNETSpain2015
Covariance (out) and contravariance (in)
public delegate T Factory<out T>();
void FillGarage(int numVehicles, Factory<Vehicle> factory) { /* ... */ }
Factory<Car> carFactory = () => new Car();
Factory<Vehicle> vehicleFactory = carFactory; // NO compile error
FillGarage(10, carFactory); // NO compile error
public delegate bool Predicate<in T>(T elm);
void SendCarsToRepair(Car c, Predicate<Car> pred) {
if (pred(c)) {
...
Predicate<Vehicle> predFord = elm => elm.Make == "Ford";
SendCarsToRepair(myCar, predFord); // NO compile error
#dotNETSpain2015
Variance in interfaces
List<Car> carList = new List<Car>();
List<Vehicle> vehList = carList; // compile error
IEnumerable<Vehicle> enumVehicles = carList; // OK !!!
class MakeComparer : IComparer<Vehicle> {
public int Compare(Vehicle x, Vehicle y) { /* ... */
IComparer<Car> makeComp = new MakeComparer(); // OK !!!
Car[] cars = new Car[10];
Array.Sort(cars, makeComp); // OK
#dotNETSpain2015
Variance in interfaces
Covariant:
IEnumerable<out T>
IEnumerator<out T>
IQueryable<out T>
IGrouping<out TKey, out TElement>
Contravariant:
IComparer<in T>
IEqualityComparer<in T>
IComparable<in T>
#dotNETSpain2015
Numbers
#dotNETSpain2015
Special numbers
int a = ???a == -a
int b = ???b == -b
double d = ???d != d
#dotNETSpain2015
Special numbers
int a = 0a == -a
int b = int.MinValueb == -b
double d = double.NaNd != d But d.Equals(d) !!!
#dotNETSpain2015
int a = 5, b = 0;
int c = a / b; ???
int d = a % b; ???
int a = Integer.MaxValue - 2;
int b = a + 3; ???
int a = 1000000000;
int b = a * 2000000000; ???
#dotNETSpain2015
In integer arithmetics, exceptions thrown in division by 0
int a = 5, b = 0;
int c = a / b; // DivideByZeroException
int d = a % b; // DivideByZeroException
int a = int.MaxValue - 2;
int b = a + 3; // b = -2147483646
int a = 1000000000;
int b = a * 2000000000; // b = 1321730048
#dotNETSpain2015
long l = 1000000000000L;
int i = (int) l; ???
double d = Math.Sqrt(-1); ???
double a = 5 , b = 0; ???
double c = a % b; ???
double d = a / b; ???
#dotNETSpain2015
No exceptions in floating -point arithmetics
long l = 1000000000000L;
int i = (int) l; // i = -727379968
double d = Math.Sqrt(-1); // d = NaN
double a = 5 , b = 0;
double c = a % b; // c = NaN
double d = a / b; // d = Infinity
#dotNETSpain2015
Castings double to int
double nan = double.NaN;
double inf = double.PositiveInfinity;
double bignum = 1e20;
int inan = (int) nan; ???
int iinf = (int) inf; ???
int ibignum = (int) bignum; ???
#dotNETSpain2015
Castings double to int
int inan // int.MinValue
int iinf // int.MinValue
int ibignum // int.MinValue
#dotNETSpain2015
Floating-point precision
double a = Double.MaxValue
double b = a + 1; ???
#dotNETSpain2015
No floating-point when high precision is required
double a = Double.MaxValue;
double b = a + 1;
// a = b = 1.79769313486232E308
double.Epsilon -> min. number different to 0
#dotNETSpain2015
Always use checked integer arithmetic
(checked not available for floating-point arithmetic)
checked {...}
System.ArithmeticException
System.DivideByZeroException
System.NotFiniteNumberException
System.OverflowException
#dotNETSpain2015
High precision floating-point
decimal (28 significant bits)
NO BigDecimal
BigInteger
#dotNETSpain2015
Watch out number overflows
Extra, Extra - Read All About It: Nearly All Binary
Searches and Mergesorts are Broken1: public static int BinarySearch(int[] a, int key) {
2: int low = 0;
3: int high = a.length - 1;
4:
5: while (low <= high) {
6: int mid = (low + high) / 2;7: int midVal = a[mid];
8:
9: if (midVal < key)
10: low = mid + 1
11: else if (midVal > key)
12: high = mid - 1;
13: else
14: return mid; // key found
15: }
16: return -(low + 1); // key not found.
17: }
#dotNETSpain2015
Right solution
int mid = low + ((high - low) / 2);
unsigned mid = (low + high) >> 1;
#dotNETSpain2015
Mutability
#dotNETSpain2015
What's an immutable class ?
public class Point {
private readonly int x,y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public virtual int X { get { return x; } }
public virtual int Y { get { return y; } }
...
void setPoint(Point p) ...
#dotNETSpain2015
They must be sealed (or all setters)
public class MyPoint : Point {
private int x2,y2;
public override int X {
get { return x2; }
set { x2 = value; }
}
...
MyPoint p = new MyPoint(10,20);
miobj.setPoint(p);
p.X = 30; // VALUE CHANGED
#dotNETSpain2015
Watch out maps and sets with mutable objects
var map = new Dictionary<Point,string>();
Point p = new Point(10,20);
map[p] = "my point";
Console.WriteLine(map[p]);
p.X = 11;
Console.WriteLine(map[p]);
#dotNETSpain2015
Watch out maps and sets with mutable objects
var map = new Dictionary<Point,string>();
Point p = new Point(10,20);
map[p] = "my point";
Console.WriteLine(map[p]); // "my point"
p.X = 11;
Console.WriteLine(map[p]); // KeyNotFoundException
#dotNETSpain2015
Can we bypass invariants ?
class Point {
public X { get; set;}
public Y {get; set; }
...
class Line {
public Point StartPoint {
get { return startPoint; }
set {
if (value.X > 0 && value.Y > 0) {
startPoint = value;
}
...
#dotNETSpain2015
Never returns mutable objects
Always set a copy of mutable objects
Point p = new Point (1,1);
line.StartPoint = p;
p.X = -10;
Point p = line.StartPoint;
p.X = -10;
#dotNETSpain2015
public class Student {
public String Name { get; private set; }
public List<Course> Courses { get; private set; }
...
#dotNETSpain2015
Lists are mutable
And non-empty arrays are mutable too
List<Course> list = student.Courses;
list.Add(myNewCourse);list.Clear();
#dotNETSpain2015
To create immutable lists
Enumerable.Empty<T>
list.AsReadOnly
ReadOnlyCollection
#dotNETSpain2015
Mutable and immutable classes
Prefer immutable classes. For value objects try to build it at the beginning and avoid set methods.
Make defensive copies where necessary
#dotNETSpain2015
Inheritance
#dotNETSpain2015
Inheritance
In general prefer composition over
inheritance.
Inheritance breaks encapsulation.
#dotNETSpain2015
Inheritance
public class InstrumentedHashSet<E> : HashSet<E> {
public int addCount;
public bool add(T item) {
addCount++;
return base.Add(item);
}
public static void Method(HashSet<string> a, string value) {
a.Add(value);
….
}
var set = new Instrumented<string>();
Method(set, "hello");
Method(set, "bye");
#dotNETSpain2015
Inheritance
Returns 0 !!!
public new bool add(T item)
Warning CS0108 'Instrumented<T>.Add(T)' hides inherited member
'HashSet<T>.Add(T)'. Use the new keyword if hiding was intended.
#dotNETSpain2015
Have to know intimately the class you inherit from
public class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0;
@Override public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
...
InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.addAll(Arrays.asList("How", "are", "you"));
System.out.println(s.addCount()); ???
#dotNETSpain2015
Problem when using overridable methods internally
Returns 6 !!!
We need to know what public/overridable methods do internally.
#dotNETSpain2015
Better to delegate
public class InstrumentedHashSet<E> extends Set<E> {
private int addCount = 0;
private Set<E> set = new HashSet<E>();
@Override public boolean add(E e) {
addCount++;
return set.add(e);
}
@Override public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return set.addAll(c);
}
…….
#dotNETSpain2015
Constructors and inheritance
public class Child : Parent {private int value;public Child(int value) {
this.value = value;}}
// What's the difference ???public Child(int value) : base() {
this.value = value;}
#dotNETSpain2015
Constructors and inheritance
public class Parent {private int x;public Parent(int x) {
this.x = x;...
public Child(string value) {this.value = value; // Is super called ???
}
#dotNETSpain2015
Sub sub = new Sub("hello"); // WHAT'S PRINTED ?
public class Super {
public Super() { Init(); }
virtual public void Init() { }
}
public class Sub : Super {
private String msg;
Sub(string msg) { this.msg = msg; }
override public void Init() {
msg = msg.ToUpper();
Console.WriteLine("MESSAGE: " + msg);
}
}
#dotNETSpain2015
Problem when calling overridable methods in constructor
Constructors don't have to call overridable
methods
// NullReferenceException
The method is called from parent constructor
when the child part is not initialized yet.
#dotNETSpain2015
Equals and inheritance
Resharper / Generate equality members
public class Point
{
public int X {get; set; }
public int Y {get; set; }
}
#dotNETSpain2015
Equals and inheritance
protected bool Equals(Point other)
{ return X == other.X && Y == other.Y; }
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Point) obj);
}
Exactly the same type as "this"
#dotNETSpain2015
Liskov substitution principle is not held
If S is a subtype of T, then objects of type T in a computer program can be replaced by objects of type S (that is, objects of type S can be replacements for objects of type T), without changing any important property of the program.
public class ColorPoint : Point {
public string Color {get; set; }
...
var p1 = new Point {X=10, Y=20 };
var p2 = new Point {X=10, Y=20};
var pcolor = new ColorPoint {X=10, Y=20, Color="blue"};
p1.Equals(p2) // TRUE
p1.Equals(pcolor); // FALSE
#dotNETSpain2015
Equals and inheritance
Point:
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
var other = obj as Point;
return other != null && Equals(other);
}
ColorPoint:
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
var other = obj as ColorPoint;
return other != null && Equals(other);
}
protected bool Equals(ColorPoint other) {
return base.Equals(other) &&
string.Equals(Color, other.Color);
}
Equal or subtype of "Point"
#dotNETSpain2015
Symmetry rule is not held
var p = new Point {X=10, Y=20 };
var pcolor = new ColorPoint {X=10, Y=20,
Color="blue"};
p1.Equals(pcolor) // TRUE
pcolor.Equals(p1); // FALSE
#dotNETSpain2015
So we can make a special check for parent classes
ColorPoint:
public override bool Equals(object obj)
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (!(obj is Point)) return false;
if (!(obj is ColorPoint)) return obj.Equals(this);
var other = obj as ColorPoint;
return other != null && Equals(other);
}
#dotNETSpain2015
Transitivity rule is not held
var pcolor1 = new ColorPoint {X=10, Y=20,
Color="blue"};
var pcolor2 = new ColorPoint {X=10, Y=20,
Color="red"};
var p = new Point {X=10, Y=20 };
pcolor1.Equals(p) // TRUE
p.Equals(pcolor2) // TRUE
pcolor1.Equals(pcolor2); // FALSE
#dotNETSpain2015
Quiz 1
uint[] foo = new uint[10];
object bar = foo;
Console.WriteLine("{0} {1} {2} {3}",
foo is uint[], // True
foo is int[], // ???
bar is uint[], // True
bar is int[]); // ???
#dotNETSpain2015
Quiz 1
uint[] foo = new uint[10];
object bar = foo;
Console.WriteLine("{0} {1} {2} {3}",
foo is uint[], // True
foo is int[], // False
bar is uint[], // True
bar is int[]); // True
#dotNETSpain2015
Quiz 1
The CLI has the concept of "assignment compatibility"... The assignment from source value to
target variable has to be "representation preserving".
One of the rules of the CLI is "if X is assignment compatible with Y then X[] is assignment
compatible with Y[]".
That is not a rule of C#. C#'s array covariance rule is "if X is a reference type implicitly
convertible to reference type Y (via a reference or identity conversion) then X[] is implicitly
convertible to Y[]". That is a subtly different rule!
http://blogs.msdn.com/b/ericlippert/archive/2009/09/24/why-is-covariance-of-value-typed-
arrays-inconsistent.aspx
#dotNETSpain2015
Quiz 2
short sss = 123;
object ooo = sss; // Boxing
// OK, compile error or runtime error ???
int iii = (int)sss;
int jjj = (int)(short)ooo;
int kkk = (int)ooo;
#dotNETSpain2015
Quiz 2
Why? Because a boxed T can only be unboxed to T
short sss = 123;
object ooo = sss; // Boxing
int iii = (int)sss; // OK
int jjj = (int)(short)ooo; // OK
int kkk = (int)ooo; // Runtime errorhttp://blogs.msdn.com/b/ericlippert/archive/2009/03/19/representation-and-identity.aspx
#dotNETSpain2015
Leo Antoli @lantoliEffective C#
¡¡¡Si te ha gustado no olvides rellenar la encuesta!!!Thanks
Y
AX B