Skip to content

Code in Depth

Sandesh Kota edited this page Mar 12, 2019 · 20 revisions

Type Design

public class Contact : ISerializable
{
  void ISerializable.GetObjectdata(SerializationInfo info, StreamingContext context)
  {
    GetObjectDataInternal(info, context);
  }
  
  protected virtual void GetObjectDataInternal(SerializationInfo info, StreamingContext context)
  {
    Console.WriteLine("Base");
  }
}

public class Customer : Contact
{
  protected override void GetObjectDataInternal(SerializationInfo info, StreamingContext context)
  {
    base.GetObjectDataInternal(info, context);
  }
}

Collections

  • Altering a List as You Iterate
    • Use a "for" loop
    • Use a second list
    • Use List.RemoveAll()
// Problem - You cannot alter list when iterating
var list = new List<int>() { 0, 1, 2, 3, 4, 5, 6 };
foreach (var item in list)
{
  if (item % 2 == 0) { list.Remove(item); }
}

// Solution ? Nope
for (int i = 0; i< GetListCount(list), i++)
{
  var item = list[i];
  if (item % 2 == 0) { list.Remove(item); }  
}
private int GetListCount<T>(List<T> list) { return list.Count(); }

// Right Way
for (int i = GetListCount(list) - 1, i >= 0 , i--)
{
  var item = list[i];
  if (item % 2 == 0) { list.Remove(item); }  
}
  • LINQ Evaluation
var list = new List<int>() { 0, 1, 2, 3, 4, 5, 6 };
var a = list.Where(x => { return true; } );
var avg = a.Average();
var count = a.Count();
foreach(var x in a) { }
// The query is evaluated 3 times

var aa = a.ToList();     // If you assign the query output to a list then the query is executed only once.
var avg = aa.Average();
var count = aa.Count();
foreach(var x in aa) { }
// The query is evaluated only once

// Wrong
for (int i = 0; i < list.Count(); i++ ) { }
// The list is iterated as many times as the number of elements in the list because of "list.Count()"
  • Which Collection ?
    • For Contains() use HashSet | looping ? list | ordered ? Adding ? Removing ? SortedSet
  • Big O Notation
  • set.UnionWith() [Updates the item] is faster than set.Union() [creates new IEnumerable]

Methods and Overloads

  • Date Airthmatic
static DateTime IncrementByYear(DateTime d)
{
  return new DateTime(d.Year + 1, d.Month, d.Day);
}

This code throws an exception on Feb 29, 2012. As it is not a valid date.
This issue caused much of Azure to Crash in Feb 2012

// Right Way
var newDate = date.AddYears(1);
  • Overload Puzzle
private string Test(Int64 item) { return "Int64"; }
private string Test(Int32 item) { return "Int32"; }
private string Test(Object item) { return "Object"; }

Int16 itemInt16 = 42;
Int16 itemInt32 = 42;
Int16 itemInt64 = 42;
DateTime itemDate = new DateTime(2006, 03, 25);

Test(itemInt32)
Test(itemInt64)
Test(itemdate)
Test(itemInt16)   => 32

// When a specific overload is not available, "closest match" is determined.
// Implicit Conversion and Explicit Compiler behavior is more ! than inheritance in fining the closest match
// So Int16's closest match Int32 is called

private string Test<T>(T item) {  return "Generic";  }
Test(itemInt16)   => "Generic"
// Presence of Generic makes the Exact match for Int16 and so the generic method is called
  • Overloads
class A
{
  public string Test(string value) { return "string"; }
  public virtual string Test(Object value) { return "object"; }
}
class A : B
{
  public override string Test(Object value) { return base.Test(value); }
}

// Test Code
(new B()).Sample("");     => "string" (direct call to A.Test(string))

// Pickups
// 1 - For candidate methods, determine the best function member
// 2 - Explicit types trump generics
// 3 - Overloads that don't require conversion trump implicit conversions
// 4 - Looks for overridden methods (after looking for specific signature in base classes)
  • HideBySig
public class A1
{
  public string Foo(Int32 x) { return "Int32 Overload"; }
  public string Foo(Int64 x) { return "Int64 base"; }
}
public class B1: A1
{
  public new string Foo(Int64 x) { return "Int64 Derived"; }
}
int i = 42;
var b1 = new B1();
b1.Foo(i)  =>  "Int64 Derived"
A1 a1 = b1;
a1.Foo(i)  =>  "Int32 Overload"
public class A2
{
  public string Foo(Int32 x) { return "Int32 Overload"; }
  public virtual string Foo(Int64 x) { return "Int64 base"; }
}
public class B2: A2
{
  public override string Foo(Int64 x) { return "Int64 Derived"; }
}
int i = 42;
var b1 = new B1();
b1.Foo(i)  =>  "Int64 Derived"
A1 a1 = b1;
a1.Foo(i)  =>  "Int64 Derived"
  • Interface
public interface IFoo
{
  string Foo(Int32 x);
  string Foo2(Int32 x);
}
public class A1 : IFoo
{
  public virtual string Foo(Int32 x) { return "Foo base"; }
  public string Foo2(Int32 x) { return "Foo2 base"; }
}
public class B1 : A1 
{
  public override string Foo(Int32 x) { return "Foo derived"; }
  public new string Foo2(Int32 x) { return "Foo2 derived"; }
}

//Test
Int32 i = 42;
var b1 = new B1();
Ifoo foo = b1;
b1.Foo();       =>   "foo derived"
b1.Foo2();      =>   ""
foo.Foo();      =>   ""
foo.Foo2();     =>   "foo2 base"
  • Override instead of HideBySig
public class A1
{
  public string Foo(int Fred) { return "base foo"; }
}
public class B1: A1
{
  public string Foo(int George) { return "derived foo"; }
}

// Test
var b1 = new B1();
b1.Foo(42);     =>    "derived foo"
b1.Foo(George: 42);     =>    "derived foo"
b1.Foo(Fred: 42);     =>    "base foo"
  • Return Type
using System;

class Test
{
    static string Foo(int x)
    {
        Console.WriteLine("Foo(int x)");
        return "";
    }
    
    static Guid Foo(double y)
    {
        Console.WriteLine("Foo(double y)");
        return Guid.Empty;
    }

    static void Main()
    {
        Guid guid = Foo(10);  => Guid Foo(double y) will be called since the other method return type cannot be assigned to Guid
    }
}

Optional Params

  • provide default in method declaration
  • Actually a compiler trick
    • The default value is coded into the method call
    • This creates versioning issues because the value is in the calling code
    • Changing default value in the declaration does not affect calling code until the calling code is recompiled
    • Can only be constants (no new instances, no new value types created with constructor like DateTime)
public class A1 
{
  public string Method(int x, int y = 0) { return "Optional"; }
  public string Method(int x) { return "Normal"; }
}

// Test
var b1 = new B1();
b1.Method(1);     =>    "Normal"

// EXAMPLE TWO
public class A1 
{
  public string Method(int x) { return "Normal"; }
  public string Method2(int x) { return "Normal"; }
}
public class B1
{
  public string Method(int x, int y = 0) { return "Optional"; }
  public string Method2(int x, int y = 0) { return "Optional"; }
  public string Method2(int x, string y = "") { return "Optional"; }
}

// Test
var b1 = new B1();
b1.Method(1);     =>    "Optional"
b1.Method2(1);     =>    COMPILE_ERROR

Floating Point Numbers

  • Operations
Single one  =  1;
Single three =  3;
Single x  =  one / three;
Double result = 3 * x ;
result ==  ?

Answers?
== 1.0, < 1.0, > 1.0 Math.Round(result, 7) == 1

What if? Single result = 3 * x ;

Reason: Base2 airthmatic

  • Example 2
Double  x  =  .1;
Double  result  =  10  *  x ;
Double   result2  =  x  + x  + x + x + x + x  + x  + x + x + x;

result == result2 ?
Console.WriteLine( "{0}  - {1}" ,  result,  result2)
Console.WriteLine( "{0:R}  - {1:R}" ,  result,  result2)

Reason: Can't be held exactly in Base 2 system so there is a rounding error

  • Division
Int maxDiscountPercent   =  30;
Int markupPercent  =  20;
Int niceFactor  =  30;
Double  discount  =   maxDiscountPercent   * ( markupPercent  /  niceFactor) ;
discount  ?

== 0

Reason: Integer division in C# truncates the value to nearest integer. So 20 / 30 becomes Zero

// Error Code:
Int x = 9, int y = 10; int z = 100;
decimal interim = x / y;
Decimal result = z / interim ;
  • Divide By Zero
Int zero   =  0;
Int three  =  3;
Int result  = 3  / 0;

// Int, Decimal => Throws exception
// Single / Double => They can hold Infinity (Double.PositiveInfinity , Double.NegativeInfinity)
  • Rounding
Math.Round(3.5);       ==> 4
Math.Round(4.5);       ==> 4

Reason: .Net rounds mid-point number to the nearest Even digit -> Banker's rounding

Math.Round(4.5, MidpointRounding.AwayFromZero);     ==> 5
  • Top Of Type
Int top  =  int.MaxValue;
Int next = top + 1;

// Int => Airthmatic overflow is not turned ON by default =>  Value rolls over to minimum value
// Decimal => overflow exception
// Single, Double => Infinity is supported => top + 1 is same as top.

Learnings:

  • Single [Base2-32bit] , Double [Base2-64bit] & Decimal [Base10-128bit]
  • Use "fuzzy" comparisons for Single & Double Data Types
  • Base 10 & Base 2 rounds differently
  • Avoid data Conversions between fractional type
  • Decimal performance is 10 times slower than Single & Double
  • Decimal for money, Double for everything else

Clone this wiki locally