Friday, January 25, 2013

Games Of Reflections

i got this task from an interview, got all this code and was asked to implement the Serialize class. there r a few points:
1 - circular reference (of person)
2 - after Deserialize the mutual Address object must be the same again, as in only 1 instance.
so here is what i did:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using System.Data;

namespace SerializeExercise
{
  class FullName
  {        
    public bool Equals(FullName other)
    {
      return string.Equals(_firstName, other._firstName)
             && string.Equals(_lastName, other._lastName);
    }
       
   
    public override bool Equals(object obj)
    {            
      if (ReferenceEquals(null, obj)) return false;
      return obj is FullName && Equals((FullName)obj);
    }
       
    public override int GetHashCode()
    {            
      unchecked
      {                
        return ((_firstName != null ? _firstName.GetHashCode() : 0) * 397)
               ^ (_lastName != null ? _lastName.GetHashCode() : 0);
      }
    }


   
public static bool operator ==(FullName left, FullName right)
    {    return left.Equals(right);    }

    
public static bool operator !=(FullName left, FullName right)
    {    return !left.Equals(right);   }

   
private string _firstName;
    private string _lastName;

    public FullName() { }

    public FullName(string firstName, string lastName)
    {
       _firstName = firstName;
       _lastName = lastName;
    }


   
public string FirstName
    {    get { return _firstName; }
         set { _firstName = value; }    }

   
public string LastName
    {    get { return _lastName; }
         set { _lastName = value; }    }
}

class Address
{        
  protected bool Equals(Address other)
  {
    return string.Equals(Street, other.Street)
           && string.Equals(City, other.City)
           && string.Equals(ZipCode, other.ZipCode)
           && string.Equals(PhoneNumber, other.PhoneNumber);
  }

  
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((Address)obj);
  }

  
public override int GetHashCode()
  {            
    unchecked
    {
      int hashCode = (Street != null ? Street.GetHashCode() : 0);
      hashCode = (hashCode * 397) ^ (City != null ? City.GetHashCode() : 0);
      hashCode = (hashCode * 397) ^ (ZipCode != null ? ZipCode.GetHashCode() : 0);
      hashCode = (hashCode * 397)
                 ^ (PhoneNumber != null ? PhoneNumber.GetHashCode() : 0);
      return hashCode;
    }
  }


 
public static bool operator ==(Address left, Address right)
  {    return Equals(left, right);    }

 
public static bool operator !=(Address left, Address right)
  {    return !Equals(left, right);    }

  public string Street { get; set; }
  public string City { get; set; }
  public string ZipCode { get; set; }
  public string PhoneNumber { get; set; }
}

class Person
{
  protected bool Equals(Person other)
  {            
    return Name.Equals(other.Name) && Age == other.Age && Equals(Address, other.Address);
  }

 
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((Person)obj);
  }

  public override int GetHashCode()
  {            
    unchecked
    {                
      int hashCode = Name.GetHashCode();
      hashCode = (hashCode * 397) ^ Age;
      hashCode = (hashCode * 397) ^ (Address !=
null ? Address.GetHashCode() : 0);
      return hashCode;
    }
  }


 
public static bool operator ==(Person left, Person right)
  {    return Equals(left, right);    }

 
public static bool operator !=(Person left, Person right)
  {    return !Equals(left, right);    }

 
public FullName Name { get; set; }
  public int Age { get; set; }
  public Address Address { get; set; }
  public Person Spouse { get; set; }
}

class Program
{
  static void Main(string[] args)
  {            
    var alon = new Person
    {
       Name =
new FullName("Alon", "Aloni"),
       Age = 43,
       Address =
new Address
       {
         Street =
"Streety 10",
         City = "Big City",
         ZipCode = "30500",
         PhoneNumber = "+972-123456789"
       }
    };


    var liat = new Person
    {
       Name =
new FullName("Liat", "Liati"),
       Age = 40,
       Address = alon.Address,
       Spouse = alon
    };
    alon.Spouse = liat;

    
using (var buffer = new MemoryStream())
    {                
       Serializer.Serialize(liat, buffer);
       buffer.Seek(0, SeekOrigin.Begin);
       var newLiat = Serializer.Deserialize<Person>(buffer);

       Trace.Assert(liat == newLiat, "Fail, object are not logicaly the same");
       Trace.Assert(newLiat != null
                    && newLiat.Spouse != null
                    && newLiat.Spouse.Address != null
                    && newLiat.Address != null
                    && ReferenceEquals(newLiat.Address, newLiat.Spouse.Address),
               "Address is not the same object");
       Trace.Assert(newLiat != null
                    && newLiat.Spouse != null
                    && newLiat.Spouse.Spouse != null
                    && ReferenceEquals(liat.Spouse.Spouse, liat),
              "The new Liat and new Alon don't refer each other");
    }
   }
  }


//and this is how i got Serializer:
  public class Serializer
 {   
    public static void Serialize<T>(T o, Stream stream) {}      
       public static T Deserialize<T>(Stream stream){}
 }
}



so this is currently my solution - i am breaking all objects to table and create something similar to what i would have done if i would have wanted to save the data to a DB, serialize all that while putting the hashcode values wherever i need to see an object. in both Serialize and Deserialize i used dictionaries to manage the objects. i used dataset for comfort reasons.
public class Serializer
{  public static void Serialize<T>(T o, Stream stream)
  {           

    var _2DDict = CreateDict2D(o);
    DataSet ds = new DataSet("miniDB");
    foreach (var entry in _2DDict)
    {               

      Type t = entry.Key;
      DataTable dt = new DataTable(t.FullName);
      foreach (var p in t.GetProperties())
      {                   

        if (p.PropertyType.Namespace == o.GetType().Namespace)
          dt.Columns.Add(p.Name, typeof(int)); // hashcode
        else
         dt.Columns.Add(p.Name, p.PropertyType);
      }
      ds.Tables.Add(dt);

      foreach (var entry2 in entry.Value)
      {

        var newRow = dt.NewRow();
        foreach (var p2 in entry2.Value.GetType().GetProperties())
        {
          if (p2.PropertyType.Namespace == o.GetType().Namespace)
            newRow[p2.Name] = p2.GetValue(entry2.Value, null).GetHashCode();
          else
            newRow[p2.Name] = p2.GetValue(entry2.Value, null);
        }
        dt.Rows.Add(newRow);
      }
    }
    ds.WriteXml(stream, XmlWriteMode.WriteSchema);
  }

  static Dictionary<Type, Dictionary<int, object>> CreateDict2D(object o)
  {           

    var myTypes = o.GetType().GetProperties().Select(p => p.PropertyType)
                    .Where(p => p.IsClass).Distinct().ToList();
    if (!myTypes.Contains(o.GetType()))
      myTypes.Add(o.GetType());

    var type_hashcode_object = new Dictionary<Type, Dictionary<int, object>>();
    foreach (var t in myTypes)
      type_hashcode_object.Add(t, new Dictionary<int, object>());

    InsertMyFields(o, type_hashcode_object); 
    return type_hashcode_object;
  }

  static void InsertMyFields(object o,

         Dictionary<Type, Dictionary<int, object>> type_hashcode_object)
  {           

    var hashcode_typeDict = (Dictionary<int, object>)type_hashcode_object[o.GetType()];
    if (!hashcode_typeDict.ContainsKey(o.GetHashCode()))
    {
      hashcode_typeDict.Add(o.GetHashCode(), o);               

      foreach (var p in o.GetType().GetProperties())
        if (p.PropertyType.Namespace == o.GetType().Namespace)
          InsertMyFields(p.GetValue(o, null), type_hashcode_object);
    }
  }

  

  public static T Deserialize<T>(Stream stream)
  {           

    DataSet ds = new DataSet();
    ds.ReadXml(stream, XmlReadMode.ReadSchema);
    var
 _2DDict = new Dictionary<Type, Dictionary<int, object>>();
    foreach (DataTable tbl in ds.Tables) 
      _2DDict.Add(Type.GetType(tbl.TableName), new Dictionary<int, object>());
   

    DataTable tblT = null;
    foreach (DataTable tbl in ds.Tables)
    {               

      if (tbl.TableName == typeof(T).FullName)
      {
        tblT = tbl;                   

        continue;
      }

      foreach (DataRow r in tbl.Rows)
      {                   

        var obj = Activator.CreateInstance(Type.GetType(tbl.TableName));
        foreach (DataColumn col in tbl.Columns)
          obj.GetType().GetProperties().Where(p => p.Name == col.ColumnName)

                       .Single().SetValue(obj, r[col], null);
        _2DDict[Type.GetType(tbl.TableName)].Add(obj.GetHashCode(), obj);
      }
    }           

  
    //var child_hashcode_T_dict = new Dictionary<int[], T>(); -- by theory...
    var child_hashcode_T_dict = new Dictionary<int, T>();
    var hashcode_T_dict = _2DDict[Type.GetType(tblT.TableName)];
    T main = default(T);
    var first = true;
    foreach (DataRow r in tblT.Rows)
    {               

      var obj = (T)Activator.CreateInstance(Type.GetType(tblT.TableName));
      if (first)
      {
        first = false;
        main = obj;
      }               

      int child_hashcode = -1;
      foreach (DataColumn col in tblT.Columns)
      {                   

        var prop = obj.GetType().GetProperties()
                     .Where(p => p.Name == col.ColumnName).Single();
        if (prop.PropertyType.Namespace == typeof(T).Namespace)
        {                       

          if (prop.PropertyType == typeof(T))
          {
            child_hashcode = Convert.ToInt32(r[col]);
            continue;
          }
          var requestedHashcode = Convert.ToInt32(r[col]);
          prop.SetValue(obj, _2DDict[prop.PropertyType][requestedHashcode], null);
        }
        else
          prop.SetValue(obj, r[col], null);
      }               

      //_2DDict[Type.GetType(tblT.TableName)].Add(obj.GetHashCode(), obj);
      hashcode_T_dict.Add(obj.GetHashCode(), obj);
      child_hashcode_T_dict.Add(child_hashcode, obj);
    }

   
    foreach (var entry in child_hashcode_T_dict)
    {               

      //var childProp = entry.Value.GetType().GetProperties()
                          .Where(cp => cp.PropertyType == typeof(T));
      //in theory i should be able to do foreach...
      var childProp = entry.Value.GetType().GetProperties()

                        .Where(cp => cp.PropertyType == typeof(T)).Single();
      childProp.SetValue(entry.Value, hashcode_T_dict[entry.Key], null);
    }           

  return main;
  }
}