| 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
|