Effective C#

Post on 15-Jul-2015

530 views 1 download

Tags:

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

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