Tekst-serialisering
Vejledende løsninger

 

 

1

Først og fremmest er der selve Building-klassen:

Building.cs
public class Building : TextSerializable {
  private string address;
  private int floors; // etager
  private int constructionYear;

  public Building() {
  }

  public Building( string address, int floors, int constructionYear ) {
    this.address = address;
    this.floors = floors;
    this.constructionYear = constructionYear;
  }

  public string ToText() {
    TextSerializer serializer = new TextSerializer( this );

    serializer.Add( address );
    serializer.Add( floors );
    serializer.Add( constructionYear );

    return serializer.Text;
  }

  public void FromText( string s ) {
    TextDeserializer deserializer = new TextDeserializer( s );

    address = deserializer.NextString();
    floors = deserializer.NextInt();
    constructionYear = deserializer.NextInt();
  }

  public override string ToString() {
    return "[Building: address=\"" + address +
                     "\", floors=" + floors +
             ", constructionYear=" + constructionYear + "]";
  }
}
Dernæst skal der indsættes følgende case i ObjectBuilder-klassen:
ObjectBuilder.cs
...
					  
case "Building":
  obj = new Building();
  break;

...
Til sidst har vi en testanvendelse:
Program.cs
class Program {

  static void Main() {
    Building building = new Building( "Gågade 1", 3, 1960 );

    string text = building.ToText();

    Console.WriteLine( text );

    Building other = (Building) ObjectBuilder.CreateObject( text );

    Console.WriteLine( other );
  }
}
Building|Gågade 1|3|1960
[Building: address="Gågade 1", floors=3, constructionYear=1960]

Kildetekst: TekstSerialisering - Building.zip

2

Først er der hjælpe-klassen InstanceVariable, der bruges til at repræsentere en instans-variabel med navn, type og værdi:

InstansVariable.cs
class InstansVariable {
  private string name, type;
  private object value;

  public InstansVariable( string name, object value )
    : this( name, value.GetType().FullName, value ) {
  }

  public InstansVariable( string name, string type, object value ) {
    this.name = name;
    this.type = type;
    this.value = value;
  }

  public string Name { get { return name; } }
  public string Type { get { return type; } }

  public string Value { get { return value.ToString(); } }
}
Bemærk, de to konstruktorer. Den første bruges i forbindelse med serialiseringen hvor man har instansvariablene, mens den anden bruges ved deserialiseringen; hvor man kun har den tekstuelle repræsentation, og derfor kun kender typen af navn.
Den ændrede TextSerializer-klasse bliver:
TextSerializer.cs
class TextSerializer {
  public const char DELIMITER = '|';

  private object obj;
  private ArrayList variables;

  public TextSerializer( object obj ) {
    this.obj = obj;

    variables = new ArrayList();
  }

  public void Add( string name, object value ) {
    variables.Add( new InstansVariable( name, value ) );
  }

  public string Text {
    get {
      StringBuilder sb = new StringBuilder();

      sb.Append( obj.GetType().Name );

      foreach ( InstansVariable variable in variables ) {
        sb.Append( DELIMITER + variable.Name );
        sb.Append( DELIMITER + variable.Type );
        sb.Append( DELIMITER + variable.Value );
      }

      return sb.ToString();
    }
  }
}
Ændringerne i TextSerializer er begrænsede: metoderne er de samme, men det er nu instanser af InstanceVariable vi samler på, hvor det tidligere kun var værdierne.
Var ændringerne i TextSerializer til at overskue, så er ændringen af TextDeserializer næsten total:
TextDeserializer.cs
class TextDeserializer {
  private char[] delimiters = { TextSerializer.DELIMITER };

  private ArrayList variables;
  private string className;
  private int current;

  public TextDeserializer( string text ) {
    variables = new ArrayList();
    string[] tokens = text.Split( delimiters );

    className = tokens[ 0 ];

    for ( int i = 1; i < tokens.Length; i += 3 )
      variables.Add(
        new InstansVariable( tokens[ i ], tokens[ i+1 ], tokens[ i+2 ] ) );

    current = -1;
  }

  public string ClassName { get { return className; } }

  public bool Next() {
    return ++current < variables.Count;
  }

  private InstansVariable Current() {
    return (InstansVariable) variables[ current ];
  }

  public string CurrentName { get { return Current().Name; } }
  public string CurrentType { get { return Current().Type; } }
  public string CurrentString { get { return Current().Value; } }

  public int CurrentInt { get { return Int32.Parse( CurrentString ); } }
  public bool CurrentBoolean
    { get { return Boolean.Parse( CurrentString ); } }
}
Vi har her valgt at bruge properties i forbindelse med diverse Current-ting, og man bemærker at det meste af processen i denne klasse faktisk ligger i konstruktoren, der parser den tekstuelle repræsentation af objektet.
Endelig er der anvendelsen i Person-klassen, der bruger disse to klasser:
Person.cs
class Person : TextSerializable {
  private string name;
  private string address;
  private int age;
  private bool male;

  public Person() {
  }

  public Person( string name, string address, int age, bool male ) {
    this.name = name;
    this.address = address;
    this.age = age;
    this.male = male;
  }

  public string ToText() {
    TextSerializer serializer = new TextSerializer( this );

    serializer.Add( "name", name );
    serializer.Add( "address", address );
    serializer.Add( "age", age );
    serializer.Add( "male", male );

    return serializer.Text;
  }

  public void FromText( string s ) {
    TextDeserializer deserializer = new TextDeserializer( s );

    deserializer.Next();
    name = deserializer.CurrentString;
    deserializer.Next();
    address = deserializer.CurrentString;
    deserializer.Next();
    age = deserializer.CurrentInt;
    deserializer.Next();
    male = deserializer.CurrentBoolean;
  }

  public override string ToString() {
    return "[Person: name=\"" + name +
             "\", address=\"" + address +
                   "\", age=" + age +
                    ", male=" + male + "]";
  }
}
Tvivlsom gevinst Som det ses af deserialiseringen, har klasser der implementerer TextSerializable næppe gavn af de ekstra oplysninger i den tekstuelle repræsentation - simple klasser som Person-klassen har ihvertfald ikke! Og samtidig skal man nu bruge to kald hver gang man skal have oplysning om en instans-variabel, da Next og Current nu er seperate kald.
Til sidst har vi en testanvendelse:
Program.cs
class Program {

  static void Main() {
    Person person = new Person( "Jens Jensen", "Århus", 30, true );

    string text = person.ToText();

    Console.WriteLine( text );

    Person other = (Person) ObjectBuilder.CreateObject( text );

    Console.WriteLine( other );
  }
}
Person|name|System.String|Jens Jensen|address|System.String|Århus|age|
System.Int32|30|male|System.Boolean|True
[Person: name="Jens Jensen", address="Århus", age=30, male=True]
Bemærk, at instans-variablenes type-navne er inkl. namespace.

Kildetekst: TekstSerialisering - Name and Type.zip

3

Lad os først se de to klasser der nævnes i "hintet".

Først er der serialiseringen af et metode-kald, der hjælpes på vej af følgende klasse:
CallSerializer.cs
class CallSerializer {
  public const char DELIMITER = (char) 0xFFFF;

  private string procedureName;
  private ArrayList parameters;

  public CallSerializer( string procedureName ) {
    this.procedureName = procedureName;

    parameters = new ArrayList();
  }

  public void Add( TextSerializable parameter ) {
    parameters.Add( parameter );
  }

  public string Text {
    get {
      StringBuilder sb = new StringBuilder();

      sb.Append( procedureName );

      foreach ( TextSerializable parameter in parameters )
        sb.Append( DELIMITER + parameter.ToText() );

      return sb.ToString();
    }
  }
}
Som det ses er CallSerializer-klassen opbygget på samme måde som TextSerializer-klassen
Dernæst er der deserialiseringen af metodekald der har følgende hjælpe-klasse:
CallDeserializer.cs
class CallDeserializer {
  private char[] delimiters = { CallSerializer.DELIMITER };

  private string[] tokens;
  private int current;

  public CallDeserializer( string text ) {
    tokens = text.Split( delimiters );
    current = 1;
  }

  public string ProcedureName { get { return tokens[ 0 ]; } }

  public bool HasMoreParameters() {
    return current < tokens.Length;
  }

  public TextSerializable NextParameter() {
    return ObjectBuilder.CreateObject( tokens[ current++ ] );
  }
}
Som det gjorde sig gældende for CallSerialiser, er også CallDeserializer opbygget på samme måde som sit modstykke: TextDeserializer.
Selve netværksprogrammeringen skal vi ikke fordybe os i, da det ikke er emnet for dette kapitel. Dog skal vi se ClientWorker'en, da den opfanger og udfører metode-kaldet, og returnerer resultatet:
ClientWorker.cs
...
					  
public void Run() {
  stream.ReadTimeout = 100;

  while ( !stop ) {
    ProcessCall();

    Thread.Sleep( 100 );
  }

  Console.WriteLine( "[ClientWorker] Terminated" );
}

public void ProcessCall() {
  try {
    CallDeserializer callDeserializer =
      new CallDeserializer( reader.ReadLine() );

    switch ( callDeserializer.ProcedureName ) {
      case "oldest":
        Person person1 = (Person) callDeserializer.NextParameter();
        Person person2 = (Person) callDeserializer.NextParameter();

        if ( person1 == null || person2 == null )
          Return( null );

        else if ( person1.OlderThan( person2 ) )
          Return( person1 );

        else
          Return( person2 );

        break;

      default:
        Console.WriteLine( "[ClientWorker] Unknown procedure: " +
		                   callDeserializer.ProcedureName );
        break;
    }
  }
  catch ( IOException ) {
  }
}

private void Return( TextSerializable returnValue ) {
  if ( returnValue != null )
    writer.WriteLine( returnValue.ToText() );
  else
    writer.WriteLine( "null" );
  writer.Flush();
}

...
Bemærk hvordan selve returneringen er udfaktoriseret til en service-metode: Return.
Som det ses af ClientWorker'en har Person-klassen fået en metode til at sammenligne Person'er:
Person.cs
...
					  
public bool OlderThan( Person other ) {
  return this.age > other.age;
}

...
Til sidst har vi en testanvendelse, der ikke alene består af en Main-metode, men af en klasse: TestClient:
TestClient.cs
class TestClient {
  private int port;
  private StreamWriter writer;
  private StreamReader reader;

  public TestClient( int port ) {
    this.port = port;
  }

  public void Start() {
    new Thread( new ThreadStart( this.Run ) ).Start();
  }

  public void Run() {
    try {
      Console.WriteLine( "[TestClient] Started" );

      Socket connection =
        new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );

      connection.Connect( new IPEndPoint( IPAddress.Parse( "127.0.0.1" ), port ) );

      NetworkStream stream = new NetworkStream( connection );
      writer = new StreamWriter( stream );
      reader = new StreamReader( stream );

      ArrayList parameters = new ArrayList();
      parameters.Add( new Person( "Anders And", "Paradisæblevej 4", 30, true ) );
      parameters.Add( new Person( "Onkel Joakim", "Profitbakken 1", 72, true ) );

      Person oldestPerson = (Person) Call( "oldest", parameters );

      Console.WriteLine( "[TestClient] call returned: " + oldestPerson );

      writer.Close();
      reader.Close();
      stream.Close();

      Console.WriteLine( "[TestClient] Terminated" );
    }
    catch ( Exception ) {
      Console.WriteLine( "[TestClient] Could not connect to server" );
    }
  }

  private object Call( string procedureName, ArrayList parameters ) {
    /*
     * Call procedure
     */
    CallSerializer callSerializer = new CallSerializer( procedureName );

    for ( int i = 0; i < parameters.Count; i++ )
      callSerializer.Add( (TextSerializable) parameters[ i ] );

    writer.WriteLine( callSerializer.Text );
    writer.Flush();

    /*
     * Return value
     */
    return ObjectBuilder.CreateObject( reader.ReadLine() );
  }
}
Med blåt er markeret de dele af koden der vedrører tekst-serialiseringen.
Selve Main-metoden har følgende indhold, der ikke vedrører selve tekst-serialiseringen:
Program.cs
class Program {

  static void Main( string[] args ) {
    const int PORT = 6010;

    // instantier og start server
    ClientManager server = new ClientManager( PORT );
    server.Start();

    // pause for at være sikker på at serveren er klar
    Thread.Sleep( 100 );

    // instantier og kør test-klient
    new TestClient( PORT ).Start();

    // lade tid nok gå til at testen kan gennemføres
    Thread.Sleep( 3000 );

    // hvis ikke serveren lukkes ned kan vi ikke kører programmet igen,
    // da port-nummeret vil være optaget
    server.Shutdown( true );

    Console.WriteLine( "[Main] Terminated" );
  }
}
[TestClient] Started
[ClientManager] Server online
[TestClient] call returned: [Person: name="Onkel Joakim",
address="Profitbakken 1", age=72, male=True]
[TestClient] Terminated
[ClientManager] Shutdown requested
[ClientWorker] Shutdown requested
[ClientWorker] Terminated
[ClientManager] Server offline
[Main] Terminated

Kildetekst: TekstSerialisering - RPC.zip