Inheritance question

Hello list,

Two things:

I’ve got a question regarding inheritance and abstract base classes
that I’m hoping somebody can give me some help with. I’m trying to
make some socket-based stuff work based on the descriptions in the
pickaxe book which states that “BasicSocket is an abstract base class
for other socket classes”. It also says “IPSocket is a base class for
sockets using IP as their transport. TCPSocket and UDPSocket are based
on this class”. I’ve successfully implemented the class method on
IPSocket, but when I wanted to make TCPSocket.addr and
TCPSocket.peeraddr work (both methods should be inherited from
IPSocket), I had to write this functionality directly into my
TCPSocketOps class - if I put it into IPSocketOps the methods are not
found. Now I have to implement UDPSocket.addr and UDPSocket.peeraddr
and I don’t want to duplicate the logic in the methods (besides, then
it’s not really inheritance is it?).

To make my question clearer, I’ll paste the code below. The methods in
question are marked with TODO’s - they sit in the TCPSocketOps class
which is marked with RubyClass(“TCPSocket”) instead of inheriting the
methods from RubyClass(“IPSocket”) and I’m questioning whether there
is another way to do this or if that’s really correct. Thanks for your
time. Also, the exception I get when I move the methods to IPSocketOps
is:

System.MissingMethodException: undefined local variable or method
`addr’ for
#Ruby::BuiltIns::TCPSocket:0x0000058:Ruby.Builtins.RubyClass
at Ruby.Builtins.Kernel.MethodMissing(CodeContext context, Object
self, BlockParam block, SymbolId name, Object[] args) in
D:\Projects\IronRuby\trunk\src\IronRuby.Libraries\Builtins\Kernel.cs:line
227

using System;
using System.Collections.Generic;
using System.Text;
using Ruby.Runtime;
using System.Net.Sockets;
using Ruby.Builtins;
using Microsoft.Scripting;
using System.Net;

namespace Ruby.BuiltIns {

[RubyClass("IPSocket")]
public static class IPSocketOps {
    [RubyMethod("getaddress", RubyMethodAttributes.PublicSingleton)]
    public static string GetAddress(object self, MutableString 

hostName) {
return
System.Net.Dns.GetHostEntry(hostName).AddressList[0].ToString();
}
}

[RubyClass("TCPSocket", Inherits = typeof(Object), Extends =

typeof(IPSocket))]
public static class TCPSocketOps {

    [RubyMethod("gethostbyname", 

RubyMethodAttributes.PublicSingleton)]
public static object GetHostByName(object self, MutableString
hostName) {
RubyArray result = ArrayOps.CreateArray();
System.Net.IPHostEntry hostEntry =
System.Net.Dns.GetHostEntry(hostName);
result.Add(hostName);
result.Add(ArrayOps.CreateArray(hostEntry.Aliases.Length));
foreach (string alias in hostEntry.Aliases)
{
((RubyArray)result[1]).Add(new MutableString(alias));
}
result.Add((int)hostEntry.AddressList[0].AddressFamily);
result.Add(new
MutableString(hostEntry.AddressList[0].ToString()));
return result;
}

    [RubyMethod("new", RubyMethodAttributes.PublicSingleton)]
    public static TCPSocket/*!*/ CreateSocket(CodeContext/*!*/

context, object self, BlockParam/!/ startRoutine, [NotNull]params
object[]/!/ args) {
return new TCPSocket(args[0], args[1]);
}

    [RubyMethod("open", RubyMethodAttributes.PublicSingleton)]
    public static TCPSocket/*!*/

CreateSocketAlias_Open(CodeContext/!/ context, object self,
BlockParam/!/ startRoutine, [NotNull]params object[]/!/ args) {
return CreateSocket(context, self, startRoutine, args);
}

    // TODO: This should move into IPSocketOps - How does

inheritance work in IronRuby?
[RubyMethod(“addr”, RubyMethodAttributes.PublicInstance)]
public static object GetAddress(IPSocket self) {
return GetAddressFromEndPoint(self.socket.LocalEndPoint);
}

    // TODO: This should move into IPSocketOps - How does

inheritance work in IronRuby?
[RubyMethod(“peeraddr”, RubyMethodAttributes.PublicInstance)]
public static object GetPeerAddress(IPSocket self) {
return GetAddressFromEndPoint(self.socket.RemoteEndPoint);
}

    public static object GetAddressFromEndPoint(EndPoint endPoint) {
        RubyArray result = ArrayOps.CreateArray();
        IPEndPoint ep = (IPEndPoint)endPoint;
        MutableString addressFamily = null;
        switch (ep.AddressFamily) {
            // Surely this already exists somewhere in System.Net?

Couldn’t find it though…
case AddressFamily.InterNetwork:
addressFamily = new MutableString(“AF_INET”);
break;
default:
throw new NotImplementedException();
}

        result.Add(addressFamily);
        result.Add(ep.Port);
        result.Add(new

MutableString(System.Net.Dns.GetHostEntry(ep.Address).HostName));
result.Add(new MutableString(ep.Address.ToString()));
return result;
}
}

public class IPSocket {
    internal Socket socket;
}

public class TCPSocket : IPSocket {
    public TCPSocket(object hostname, object port) {
        socket = new Socket(AddressFamily.InterNetwork,

SocketType.Stream, ProtocolType.Tcp);

        int portNumber = 0;

        if (port is int) {
            portNumber = (int)port;
        }
        else if (port is MutableString) {
            switch ((MutableString)port) {
                case "ftp":
                    portNumber = 21;
                    break;
                case "http":
                    portNumber = 80;
                    break;
                default:
                    throw new NotImplementedException();
            }
        }

        socket.Connect((MutableString)hostname, portNumber);
    }
}

}

Apologies - I have an itchy send finger. The other issue is that I
think the file CalcOps.cs is in the repository, but not included in
the project - so the code refuses to build cleanly on the latest
revision. I worked around this by manually adding the file to the
correct project in visual studio.

Thanks for your time

Terence

Terence L.:

Apologies - I have an itchy send finger. The other issue is that I
think the file CalcOps.cs is in the repository, but not included in
the project - so the code refuses to build cleanly on the latest
revision. I worked around this by manually adding the file to the
correct project in visual studio.

I just removed this file from SVN. It was an artifact of a demo that I
did at RubyConf …

-John

Alternatively, you may prefer not to wrap the .NET type and to extend
System.Net.Sockets.Socket into the DLR directly. This is inline with
the
extension of other .NET classes such as Int32 to Fixnum and
MutableString to
MutableString.
The following code demonstrates how you could do this…

using System;
using System.Collections.Generic;
using System.Text;
using Ruby.Runtime;
using System.Net.Sockets;
using Ruby.Builtins;
using Microsoft.Scripting;
using System.Net;

namespace Ruby.BuiltIns {

[RubyClass("IPSocket", Extends=typeof(Socket))]
public static class IPSocketOps {
    [RubyMethod("getaddress", RubyMethodAttributes.PublicSingleton)]
    public static string GetAddress(RubyClass klass, MutableString

hostName) {
return
System.Net.Dns.GetHostEntry(hostName).AddressList[0].ToString();
}

   [RubyMethod("addr", RubyMethodAttributes.PublicInstance)]
    public static object GetAddress(Socket self) {
        return GetAddressFromEndPoint(self.LocalEndPoint);
    }

    [RubyMethod("peeraddr", RubyMethodAttributes.PublicInstance)]
    public static object GetPeerAddress(Socket self) {
        return GetAddressFromEndPoint(self.RemoteEndPoint);
    }

    public static object GetAddressFromEndPoint(EndPoint endPoint) {
        RubyArray result = ArrayOps.CreateArray();
        IPEndPoint ep = (IPEndPoint)endPoint;
        MutableString addressFamily = null;
        switch (ep.AddressFamily) {
            // Surely this already exists somewhere in System.Net?

Couldn’t find it though…
case AddressFamily.InterNetwork:
addressFamily = new MutableString(“AF_INET”);
break;
default:
throw new NotImplementedException();
}

        result.Add(addressFamily);
        result.Add(ep.Port);
        result.Add(new

MutableString(System.Net.Dns.GetHostEntry(ep.Address).HostName));
result.Add(new MutableString(ep.Address.ToString()));
return result;
}
}

[RubyClass("TCPSocket", Inherits=typeof(IPSocketOps))]
public static class TCPSocket : Object{

    [RubyMethod("gethostbyname", 

RubyMethodAttributes.PublicSingleton)]
public static object GetHostByName(object self, MutableString
hostName) {
RubyArray result = ArrayOps.CreateArray();
System.Net.IPHostEntry hostEntry =
System.Net.Dns.GetHostEntry(hostName);
result.Add(hostName);
result.Add(ArrayOps.CreateArray(hostEntry.Aliases.Length));
foreach (string alias in hostEntry.Aliases) {
((RubyArray)result[1]).Add(new MutableString(alias));
}
result.Add((int)hostEntry.AddressList[0].AddressFamily);
result.Add(new
MutableString(hostEntry.AddressList[0].ToString()));
return result;
}

    [RubyMethod("new", RubyMethodAttributes.PublicSingleton)]
    public static Socket/*!*/ CreateSocket(CodeContext/*!*/ context,

object self, BlockParam/!/ startRoutine, [NotNull]params object[]/!/
args) {
object hostname = args[0];
object port = args[1];
Socket socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);

        int portNumber = 0;

        if (port is int) {
            portNumber = (int)port;
        } else if (port is MutableString) {
            switch ((MutableString)port) {
                case "ftp":
                    portNumber = 21;
                    break;
                case "http":
                    portNumber = 80;
                    break;
                default:
                    throw new NotImplementedException();
            }
        }

        socket.Connect((MutableString)hostname, portNumber);
        return socket;
    }

    [RubyMethod("open", RubyMethodAttributes.PublicSingleton)]
    public static Socket/*!*/ 

CreateSocketAlias_Open(CodeContext/!/
context, object self, BlockParam/!/ startRoutine, [NotNull]params
object[]/!/ args) {
return CreateSocket(context, self, startRoutine, args);
}

}

}

I think you are getting too carried away with the various conventions
around
extending .NET types for use by the DLR.
If you are simply wanting to wrap the underlying
System.Net.Sockets.Socket
class (rather than extend it) then you don’t need to have both IPSocket
and
IPSocketOps classes and so on. It is enough to have an abstract
non-static
class IPSocket and a concrete class TCPSocket, which derives directly
from
IPSocket. Everything then works. See the code below.

using System;
using System.Collections.Generic;
using System.Text;
using Ruby.Runtime;
using System.Net.Sockets;
using Ruby.Builtins;
using Microsoft.Scripting;
using System.Net;

namespace Ruby.BuiltIns {

[RubyClass("IPSocket")]
public abstract class IPSocket {
    internal Socket socket;

    [RubyMethod("getaddress", RubyMethodAttributes.PublicSingleton)]
    public static string GetAddress(object self, MutableString 

hostName)
{
return
System.Net.Dns.GetHostEntry(hostName).AddressList[0].ToString();
}

   [RubyMethod("addr", RubyMethodAttributes.PublicInstance)]
    public static object GetAddress(IPSocket self) {
        return GetAddressFromEndPoint(self.socket.LocalEndPoint);
    }


   [RubyMethod("peeraddr", RubyMethodAttributes.PublicInstance)]
    public static object GetPeerAddress(IPSocket self) {
        return GetAddressFromEndPoint(self.socket.RemoteEndPoint);
    }

    public static object GetAddressFromEndPoint(EndPoint endPoint) {
        RubyArray result = ArrayOps.CreateArray();
        IPEndPoint ep = (IPEndPoint)endPoint;
        MutableString addressFamily = null;
        switch (ep.AddressFamily) {
            // Surely this already exists somewhere in System.Net?

Couldn’t find it though…
case AddressFamily.InterNetwork:
addressFamily = new MutableString(“AF_INET”);
break;
default:
throw new NotImplementedException();
}

        result.Add(addressFamily);
        result.Add(ep.Port);
        result.Add(new

MutableString(System.Net.Dns.GetHostEntry(ep.Address).HostName));
result.Add(new MutableString(ep.Address.ToString()));
return result;
}
}

[RubyClass("TCPSocket")]
public class TCPSocket : IPSocket {

    [RubyMethod("gethostbyname", 

RubyMethodAttributes.PublicSingleton)]
public static object GetHostByName(object self, MutableString
hostName) {
RubyArray result = ArrayOps.CreateArray();
System.Net.IPHostEntry hostEntry =
System.Net.Dns.GetHostEntry(hostName);
result.Add(hostName);
result.Add(ArrayOps.CreateArray(hostEntry.Aliases.Length));
foreach (string alias in hostEntry.Aliases) {
((RubyArray)result[1]).Add(new MutableString(alias));
}
result.Add((int)hostEntry.AddressList[0].AddressFamily);
result.Add(new
MutableString(hostEntry.AddressList[0].ToString()));
return result;
}

    [RubyMethod("new", RubyMethodAttributes.PublicSingleton)]
    public static TCPSocket/*!*/ CreateSocket(CodeContext/*!*/ 

context,
object self, BlockParam/!/ startRoutine, [NotNull]params object[]/!/
args) {
return new TCPSocket(args[0], args[1]);
}

    [RubyMethod("open", RubyMethodAttributes.PublicSingleton)]
    public static TCPSocket/*!*/ 

CreateSocketAlias_Open(CodeContext/!/
context, object self, BlockParam/!/ startRoutine, [NotNull]params
object[]/!/ args) {
return CreateSocket(context, self, startRoutine, args);
}

    public TCPSocket(object hostname, object port) {
        socket = new Socket(AddressFamily.InterNetwork,

SocketType.Stream, ProtocolType.Tcp);

        int portNumber = 0;

        if (port is int) {
            portNumber = (int)port;
        } else if (port is MutableString) {
            switch ((MutableString)port) {
                case "ftp":
                    portNumber = 21;
                    break;
                case "http":
                    portNumber = 80;
                    break;
                default:
                    throw new NotImplementedException();
            }
        }

        socket.Connect((MutableString)hostname, portNumber);
    }
}

}