Skip to content

Instantly share code, notes, and snippets.

@LeeCampbell
Created June 4, 2013 17:16
Show Gist options
  • Save LeeCampbell/5707761 to your computer and use it in GitHub Desktop.
Save LeeCampbell/5707761 to your computer and use it in GitHub Desktop.
KDB Client rewritten from sample Kx code to use more useful names.
//2012.06.07 fixed scoping of GUID
//2012.05.29 for use with kdb+v3.0, changed handshake and added Guid. boolean v6->vt tracks type capability.
//2012.01.26 refactored clamp into clampDT, for Date.DateTime()
//2012.01.25 rz() clamp datetime to valid range
//2010.11.17 Block sending new timetypes to version of kdb+ prior to v2.6 (use prior release of KdbClient.cs for older kdb+ versions)
// Max buffer size (default 64kB) used for reading is now a parameter to the KdbClient constructor
// Date, Month, Minute, Second, KTimeSpan are now serializable, implement IComparable
// and have default constructors for xml serialization.
// Added GetNullRepresentation(Type t)
//2010.08.05 Added KException for exceptions due to server error, authentication fail and func decode
//2010.01.14 Exposed static var e (Encoding) as public
//2010.01.12 Added support for unicode encoding, defaults to ASCII
//2010.01.11 Added null checks for IsNull for UDTs Date,Month,Minute,Second,KTimespan
//2010.01.04 Added new time types (timespan->KTimespan,timestamp->DateTime), drop writing kdb+ datetime
//2009.10.19 Limit reads to blocks of 64kB to reduce load on kernel memory
//2007.09.26 0Wz to MaxValue
//2006.10.05 truncate string GetValueAt null
//2006.09.29 GetNullRepresentation KdbClient.Date class(sync with KdbClient.java)
using System;
using System.IO;
using System.Net.Sockets; //csc KdbClient.cs given >q trade.q -port 5001
namespace kx
{
public class KdbClient : TcpClient
{
/*public static void Main(string[]args){
//KdbClient.ReceiveTimeout=1000;
//KdbClient KdbClient=new KdbClient("localhost",5010);KdbClient.k(".username.sub[`trade;`MSFT.O`IBM.N]");while(true){object Read=KdbClient.k();O(GetLength(GetValueAt(Read,2)));}
KdbClient KdbClient=new KdbClient("localhost",5001);
//KdbClient.e=System.Text.Encoding.UTF8;
//O("Unicode "+KdbClient.k("`$\"KdbClient\"$0x52616e627920426ac3b6726b6c756e64204142"));
//object[]FieldNames=new object[4];FieldNames[0]=TimeOfDay();FieldNames[1]="xx";FieldNames[2]=(double)93.5;FieldNames[3]=300;tm();for(int i=0;i<1000;++i)KdbClient.k("insert","trade",FieldNames);tm();
//Flip Read=ToFlip(KdbClient.k("select sum price by sym from trade"));O("cols: "+GetLength(Read.FieldNames));O("rows: "+GetLength(Read.Values[0]));
KdbClient.Close();
}
*/
public static System.Text.Encoding e = System.Text.Encoding.ASCII;
private const int DefaultMaxBufferSize = 65536;
private const long Jan2000Ticks = (long) 8.64e11*730119;
private static readonly byte[] Gip = { 3, 2, 1, 0, 5, 4, 7, 6, 8, 9, 10, 11, 12, 13, 14, 15 };
static readonly object[] Nulls ={null,false,new Guid(),null,(byte)0,Int16.MinValue,Int32.MinValue,Int64.MinValue,(Single)Double.NaN,Double.NaN,' ',"",new DateTime(0),
new Month(Int32.MinValue),new Date(Int32.MinValue),new DateTime(0),new KTimespan(Int64.MinValue),new Minute(Int32.MinValue),new Second(Int32.MinValue),new TimeSpan(Int32.MinValue)};
private readonly int _maxBufferSize = DefaultMaxBufferSize;
private readonly Stream _currentStream;
private readonly int _serverVersion;
byte[] _buffer;
private int _currentIndex;
private byte[] b; //Perhaps the current field buffer? -LC
private int j; //Perhaps the current field index? -LC
private bool a; //EOF flag? -LC
public KdbClient(string hostname, int port)
: this(hostname, port, Environment.UserName) { }
public KdbClient(string hostname, int port, string username)
: this(hostname, port, username, DefaultMaxBufferSize) { }
public KdbClient(string hostname, int port, string username, int maxBufferSize)
{
_maxBufferSize = maxBufferSize;
Connect(hostname, port);
_currentStream = GetStream();
_buffer = new byte[2 + username.Length];
_currentIndex = 0;
Write(username + "\x3");
_currentStream.Write(_buffer, 0, _currentIndex);
if (1 != _currentStream.Read(_buffer, 0, 1))
{
_buffer = new byte[1 + username.Length];
Connect(hostname, port);
_currentStream = GetStream();
_currentIndex = 0;
Write(username);
_currentStream.Write(_buffer, 0, _currentIndex);
if (1 != _currentStream.Read(_buffer, 0, 1))
throw new KException("access");
}
_serverVersion = Math.Min(_buffer[0], (byte)3);
}
public new void Close()
{
_currentStream.Close();
base.Close();
}
public object k()
{
ReadInto(b = new byte[8]);
a = b[0] == 1;
bool c = b[2] == 1;
j = 4;
ReadInto(b = new byte[ReadInt() - 8]);
if (c)
u();
else
j = 0;
if (b[0] == 128)
{
j = 1;
throw new KException(ReadString());
}
return Read();
}
public object k(object x)
{
Write(1, x); return k();
}
public object k(string s)
{
return k(s.ToCharArray());
}
public object k(string s, object x)
{
object[] a = { s.ToCharArray(), x };
return k(a);
}
public object k(string s, object x, object y)
{
object[] a = { s.ToCharArray(), x, y };
return k(a);
}
public object k(string s, object x, object y, object z)
{
object[] a = { s.ToCharArray(), x, y, z };
return k(a);
}
public void ks(String s)
{
Write(0, s.ToCharArray());
}
public void ks(String s, Object x)
{
Object[] a = { s.ToCharArray(), x };
Write(0, a);
}
public void ks(String s, Object x, Object y)
{
Object[] a = { s.ToCharArray(), x, y };
Write(0, a);
}
public static int GetLength(object source)
{
return source is Dict
? GetLength(((Dict)source).x)
: source is Flip
? GetLength(((Flip)source).Values[0])
: source is char[]
? e.GetBytes((char[])source).Length
: ((Array)source).Length;
}
public static object GetValueAt(object x, int i)
{
object r = ((Array)x).GetValue(i);
return IsNull(r) ? null : r;
}
public static object GetNullRepresentation(Type t)
{
for (int i = 0; i < Nulls.Length; i++)
if (Nulls[i] != null && t == Nulls[i].GetType())
return Nulls[i];
return null;
}
public static bool IsNull(object x)
{
int t = -GetTypeIndex(x);
return t > 4 && x.Equals(Nulls[t]);
}
public static Flip ToFlip(object source)
{
if (GetTypeIndex(source) == 98) //If source is Flip
return (Flip)source;
Dict d = (Dict)source;
Flip a = (Flip)d.x, b = (Flip)d.y;
int m = KdbClient.GetLength(a.FieldNames), n = KdbClient.GetLength(b.FieldNames);
var x = new string[m + n];
Array.Copy(a.FieldNames, 0, x, 0, m);
Array.Copy(b.FieldNames, 0, x, m, n);
var y = new object[m + n];
Array.Copy(a.Values, 0, y, 0, m);
Array.Copy(b.Values, 0, y, m, n);
return new Flip(new Dict(x, y));
}
void u()
{
int n = 0, r = 0, f = 0, s = 8, p = s;
short i = 0;
j = 0;
byte[] dst = new byte[ReadInt()];
int d = j;
int[] aa = new int[256];
while (s < dst.Length)
{
if (i == 0)
{
f = 0xff & (int)b[d++];
i = 1;
}
if ((f & i) != 0)
{
r = aa[0xff & (int)b[d++]];
dst[s++] = dst[r++];
dst[s++] = dst[r++];
n = 0xff & (int)b[d++];
for (int m = 0; m < n; m++)
dst[s + m] = dst[r + m];
}
else
dst[s++] = b[d++];
while (p < s - 1)
aa[(0xff & (int)dst[p]) ^ (0xff & (int)dst[p + 1])] = p++;
if ((f & i) != 0)
p = s += n;
i *= 2;
if (i == 256)
i = 0;
}
b = dst;
j = 8;
}
void Write(object x)
{
int t = KdbClient.GetTypeIndex(x); Write((byte)t); if (t < 0) switch (t)
{
case -1: Write((bool)x); return;
case -2: Write((Guid)x); return;
case -4: Write((byte)x); return;
case -5: Write((short)x); return;
case -6: Write((int)x); return;
case -7: Write((long)x); return;
case -8: Write((float)x); return;
case -9: Write((double)x); return;
case -10: Write((char)x); return;
case -11: Write((string)x); return;
case -12: Write((DateTime)x); return;
case -13: Write((Month)x); return;
case -14: Write((Date)x); return;
case -15: Write((DateTime)x); return;
case -16: Write((KTimespan)x); return;
case -17: Write((Minute)x); return;
case -18: Write((Second)x); return;
case -19: Write((TimeSpan)x); return;
}
if (t == 99) { Dict r = (Dict)x; Write(r.x); Write(r.y); return; } _buffer[_currentIndex++] = 0; if (t == 98) { Flip r = (Flip)x; _buffer[_currentIndex++] = 99; Write(r.FieldNames); Write(r.Values); return; }
Write(KdbClient.GetLength(x)); switch (t)
{
case 0: foreach (object o in (object[])x) Write(o); return;
case 1: foreach (bool o in (bool[])x) Write(o); return;
case 2: foreach (Guid o in (Guid[])x) Write(o); return;
case 4: foreach (byte o in (byte[])x) Write(o); return;
case 5: foreach (short o in (short[])x) Write(o); return;
case 6: foreach (int o in (int[])x) Write(o); return;
case 7: foreach (long o in (long[])x) Write(o); return;
case 8: foreach (float o in (float[])x) Write(o); return;
case 9: foreach (double o in (double[])x) Write(o); return;
case 10: foreach (byte b in e.GetBytes((char[])x)) Write(b); return;
case 11: foreach (string o in (string[])x) Write(o); return;
case 12: foreach (DateTime o in (DateTime[])x) Write(o); return;
case 13: foreach (Month o in (Month[])x) Write(o); return;
case 14: foreach (Date o in (Date[])x) Write(o); return;
case 15: foreach (DateTime o in (DateTime[])x) Write(o); return;
case 16: foreach (KTimespan o in (KTimespan[])x) Write(o); return;
case 17: foreach (Minute o in (Minute[])x) Write(o); return;
case 18: foreach (Second o in (Second[])x) Write(o); return;
case 19: foreach (TimeSpan o in (TimeSpan[])x) Write(o); return;
}
}
void Write(int i, object x)
{
int n = ByteSize(x) + 8;
_buffer = new byte[n];
_buffer[0] = 1;
_buffer[1] = (byte)i;
_currentIndex = 4;
Write(n);
Write(x);
_currentStream.Write(_buffer, 0, n);
}
object Read()
{
int i = 0, n, t = (sbyte)b[j++];
if (t < 0)
switch (t)
{
case -1: return ReadBoolean();
case -2: return ReadGuid();
case -4: return b[j++];
case -5: return ReadShort();
case -6: return ReadInt();
case -7: return ReadLong();
case -8: return ReadFloat();
case -9: return ReadDouble();
case -10: return ReadChar();
case -11: return ReadString();
case -12: return ReadDateTime();
case -13: return ReadMonth();
case -14: return ReadDate();
case -15: return ReadDateTime2();
case -16: return ReadKTimeSpan();
case -17: return ReadMinute();
case -18: return ReadSecond();
case -19: return ReadTimeSpan();
}
if (t > 99)
{
if (t == 101 && b[j++] == 0)
return null;
throw new KException("func");
}
if (t == 99)
return new Dict(Read(), Read());
j++;
if (t == 98)
return new Flip((Dict)Read());
n = ReadInt();
switch (t)
{
case 0: object[] L = new object[n]; for (; i < n; i++) L[i] = Read(); return L;
case 1: bool[] B = new bool[n]; for (; i < n; i++) B[i] = ReadBoolean(); return B;
case 2: { Guid[] G = new Guid[n]; for (; i < n; i++)G[i] = ReadGuid(); return G; }
case 4: { byte[] G = new byte[n]; for (; i < n; i++)G[i] = b[j++]; return G; }
case 5: short[] H = new short[n]; for (; i < n; i++) H[i] = ReadShort(); return H;
case 6: int[] I = new int[n]; for (; i < n; i++) I[i] = ReadInt(); return I;
case 7: long[] J = new long[n]; for (; i < n; i++) J[i] = ReadLong(); return J;
case 8: float[] E = new float[n]; for (; i < n; i++) E[i] = ReadFloat(); return E;
case 9: double[] F = new double[n]; for (; i < n; i++) F[i] = ReadDouble(); return F;
case 10: char[] C = e.GetChars(b, j, n); j += n; return C;
case 11: String[] S = new String[n]; for (; i < n; i++) S[i] = ReadString(); return S;
case 12: DateTime[] P = new DateTime[n]; for (; i < n; i++) P[i] = ReadDateTime(); return P;
case 13: Month[] M = new Month[n]; for (; i < n; i++) M[i] = ReadMonth(); return M;
case 14: Date[] D = new Date[n]; for (; i < n; i++) D[i] = ReadDate(); return D;
case 15: DateTime[] Z = new DateTime[n]; for (; i < n; i++) Z[i] = ReadDateTime2(); return Z;
case 16: KTimespan[] N = new KTimespan[n]; for (; i < n; i++) N[i] = ReadKTimeSpan(); return N;
case 17: Minute[] U = new Minute[n]; for (; i < n; i++) U[i] = ReadMinute(); return U;
case 18: Second[] V = new Second[n]; for (; i < n; i++) V[i] = ReadSecond(); return V;
case 19: TimeSpan[] T = new TimeSpan[n]; for (; i < n; i++) T[i] = ReadTimeSpan(); return T;
}
return null;
}
void Write(bool x)
{
_buffer[_currentIndex++] = (byte)(x ? 1 : 0);
}
bool ReadBoolean()
{
return 1 == b[j++];
}
void Write(byte x) { _buffer[_currentIndex++] = x; }
byte ReadByte() { return b[j++]; }
void Write(short h) { _buffer[_currentIndex++] = (byte)h; _buffer[_currentIndex++] = (byte)(h >> 8); }
short ReadShort() { int x = b[j++], y = b[j++]; return (short)(a ? x & 0xff | y << 8 : x << 8 | y & 0xff); }
void Write(int i) { Write((short)i); Write((short)(i >> 16)); }
int ReadInt() { int x = ReadShort(), y = ReadShort(); return a ? x & 0xffff | y << 16 : x << 16 | y & 0xffff; }
void Write(Guid g) { byte[] b = g.ToByteArray(); if (_serverVersion < 3)throw new KException("Guid not valid pre kdb+3.0"); for (int i = 0; i < b.Length; i++)Write(b[Gip[i]]); }
Guid ReadGuid() { bool oa = a; a = false; int i = ReadInt(); short h1 = ReadShort(), h2 = ReadShort(); a = oa; byte[] b = new byte[8]; for (int j = 0; j < 8; j++)b[j] = ReadByte(); return new Guid(i, h1, h2, b); }
void Write(long j) { Write((int)j); Write((int)(j >> 32)); }
long ReadLong() { int x = ReadInt(), y = ReadInt(); return a ? x & 0xffffffffL | (long)y << 32 : (long)x << 32 | y & 0xffffffffL; }
void Write(float e) { byte[] b = BitConverter.GetBytes(e); foreach (byte i in b)Write(i); }
float ReadFloat()
{
byte c; float e;
if (!a) { c = b[j]; b[j] = b[j + 3]; b[j + 3] = c; c = b[j + 1]; b[j + 1] = b[j + 2]; b[j + 2] = c; } e = BitConverter.ToSingle(b, j); j += 4; return e;
}
void Write(double f) { Write(BitConverter.DoubleToInt64Bits(f)); }
double ReadDouble() { return BitConverter.Int64BitsToDouble(ReadLong()); }
void Write(char c) { Write((byte)c); }
char ReadChar() { return (char)(b[j++] & 0xff); }
void Write(string s) { byte[] b = e.GetBytes(s); foreach (byte i in b)Write(i); _buffer[_currentIndex++] = 0; }
string ReadString() { int k = j; for (; b[j] != 0; ++j);string s = e.GetString(b, k, j - k); j++; return s; }
void Write(Date d) { Write(d.i); }
Date ReadDate() { return new Date(ReadInt()); }
void Write(Minute u) { Write(u.i); }
Minute ReadMinute() { return new Minute(ReadInt()); }
void Write(Month m) { Write(m.i); }
Month ReadMonth() { return new Month(ReadInt()); }
void Write(Second v) { Write(v.i); }
Second ReadSecond() { return new Second(ReadInt()); }
void Write(TimeSpan t) { if (_serverVersion < 1)throw new KException("Timespan not valid pre kdb+2.6"); Write(IsNull(t) ? Int32.MinValue : (int)(t.Ticks / 10000)); }
TimeSpan ReadTimeSpan() { int i = ReadInt(); return new TimeSpan(IsNull(i) ? Int32.MinValue : 10000L * i); }
void Write(DateTime p) { if (_serverVersion < 1)throw new KException("Timestamp not valid pre kdb+2.6"); Write(IsNull(p) ? Int64.MinValue : (100 * (p.Ticks - Jan2000Ticks))); }
DateTime ReadDateTime() { long j = ReadLong(), d = j < 0 ? (j + 1) / 100 - 1 : j / 100; DateTime p = new DateTime(j == Int64.MinValue ? 0 : Jan2000Ticks + d); return p; }
DateTime ReadDateTime2() { double f = ReadDouble(); return Double.IsInfinity(f) ? (f < 0 ? DateTime.MinValue : DateTime.MaxValue) : new DateTime(IsNull(f) ? 0 : ClampTickValue(10000 * (long)Math.Round(8.64e7 * f) + Jan2000Ticks)); }
void Write(KTimespan t) { Write(IsNull(t) ? Int64.MinValue : (t.t.Ticks * 100)); }
KTimespan ReadKTimeSpan() { return new KTimespan(ReadLong()); }
void ReadInto(byte[] buffer)
{
int i = 0, j, n = buffer.Length;
for (; i < n; i += j)
if (0 == (j = _currentStream.Read(buffer, i, Math.Min(_maxBufferSize, n - i))))
throw new Exception("ReadInto");
}
static long ClampTickValue(long tick)
{
return Math.Min(
Math.Max(tick, DateTime.MinValue.Ticks),
DateTime.MaxValue.Ticks);
}
static int IndexOf(string[] x, string y)
{
int i = 0;
for (; i < x.Length && !x[i].Equals(y); )
++i;
return i;
}
static int ByteSize(object x)
{
int t = GetTypeIndex(x);
if (t == 99) //FieldNames is Dict
return 1 + ByteSize(((Dict)x).x) + ByteSize(((Dict)x).y);
if (t == 98) //FieldNames is Flip
return 3 + ByteSize(((Flip)x).FieldNames) + ByteSize(((Flip)x).Values);
if (t < 0)
return t == -11 //FieldNames is string
? 2 + CountBytesToNullTerminator((string)x)
: 1 + TypeSizes[-t];
var j = 6;
var n = KdbClient.GetLength(x);
if (t == 0)
for (var i = 0; i < n; ++i)
j += ByteSize(((object[])x)[i]);
else
j += n * TypeSizes[t];
return j;
}
static readonly int[] TypeSizes =
{
0, //[0] (place holder)
1, //[1] bool
16, //[2] GUID
0, //[3] (place holder)
1, //[4] byte
2, //[5] short
4, //[6] int
8, //[7] long
4, //[8] float
8, //[9] double
1, //[10] char
0, //[11] string (calculated)
8, //[12] DateTime
4, //[13] Month
4, //[14] Date
8, //[15] DateTime (duplicated)
8, //[16] KTimespan
4, //[17] Minute
4, //[18] Second
4 //[19] TimeSpan
};
static int GetTypeIndex(object x)
{
return x is bool ? -1
: x is Guid ? -2
: x is byte ? -4
: x is short ? -5
: x is int ? -6
: x is long ? -7
: x is float ? -8
: x is double ? -9
: x is char ? -10
: x is string ? -11
: x is DateTime ? -12
: x is Month ? -13
: x is Date ? -14
: x is DateTime ? -15 //Already captured by -12? -LC
: x is KTimespan ? -16
: x is Minute ? -17
: x is Second ? -18
: x is TimeSpan ? -19
: x is bool[] ? 1
: x is Guid[] ? 2
: x is byte[] ? 4
: x is short[] ? 5
: x is int[] ? 6
: x is long[] ? 7
: x is float[] ? 8
: x is double[] ? 9
: x is char[] ? 10
: x is DateTime[] ? 12
: x is DateTime[] ? 15 //Already captured by 12? -LC
: x is KTimespan[] ? 16
: x is TimeSpan[] ? 19
: x is Flip ? 98
: x is Dict ? 99
: 0;
}
static string PadInt(int i)
{
return String.Format("{0:00}", i);
}
static int CountBytesToNullTerminator(string s)
{
int i = s.IndexOf('\0');
i = -1 < i ? i : s.Length;
return e.GetBytes(s.Substring(0, i)).Length;
}
public class Dict
{
public object x;
public object y;
public Dict(object X, object Y)
{
x = X;
y = Y;
}
}
public class Flip
{
public string[] FieldNames;
public object[] Values;
public Flip(Dict source)
{
FieldNames = (string[])source.x;
Values = (object[])source.y;
}
public object Value(string s)
{
return Values[IndexOf(FieldNames, s)];
}
}
[Serializable]
public class Date : IComparable
{
public int i; private Date() { }public Date(int x) { i = x; }
public DateTime DateTime() { return i == -int.MaxValue ? System.DateTime.MinValue : i == int.MaxValue ? System.DateTime.MaxValue : new DateTime(i == Int32.MinValue ? 0L : ClampTickValue((long)8.64e11 * i + Jan2000Ticks)); }
public Date(long x) { i = x == 0L ? Int32.MinValue : (int)(x / (long)8.64e11) - 730119; }public Date(DateTime z) : this(z.Ticks) { }
public override string ToString() { return i == Int32.MinValue ? "" : this.DateTime().ToString("d"); }
public override bool Equals(object o) { if (o == null)return false; if (this.GetType() != o.GetType())return false; Date d = (Date)o; return i == d.i; }
public override int GetHashCode() { return i; }
public int CompareTo(object o) { if (o == null)return 1; Date other = o as Date; if (other == null)return 1; return i.CompareTo(other.i); }
}
[Serializable]
public class Month : IComparable
{
public int i; private Month() { }public Month(int x) { i = x; }
public override string ToString() { int m = 24000 + i, y = m / 12; return i == Int32.MinValue ? "" : PadInt(y / 100) + PadInt(y % 100) + "-" + PadInt(1 + m % 12); }
public override bool Equals(object o) { if (o == null)return false; if (this.GetType() != o.GetType())return false; Month m = (Month)o; return i == m.i; }
public override int GetHashCode() { return i; }
public int CompareTo(object o) { if (o == null)return 1; Month other = o as Month; if (other == null)return 1; return i.CompareTo(other.i); }
}
[Serializable]
public class Minute : IComparable
{
public int i; private Minute() { }public Minute(int x) { i = x; }
public override string ToString() { return i == Int32.MinValue ? "" : PadInt(i / 60) + ":" + PadInt(i % 60); }
public override bool Equals(object o) { if (o == null)return false; if (this.GetType() != o.GetType())return false; Minute m = (Minute)o; return i == m.i; }
public override int GetHashCode() { return i; }
public int CompareTo(object o) { if (o == null)return 1; Minute other = o as Minute; if (other == null)return 1; return i.CompareTo(other.i); }
}
[Serializable]
public class Second : IComparable
{
public int i; private Second() { }public Second(int x) { i = x; }
public override string ToString() { return i == Int32.MinValue ? "" : new Minute(i / 60).ToString() + ':' + PadInt(i % 60); }
public override bool Equals(object o) { if (o == null)return false; if (this.GetType() != o.GetType())return false; Second s = (Second)o; return i == s.i; }
public override int GetHashCode() { return i; }
public int CompareTo(object o) { if (o == null)return 1; Second other = o as Second; if (other == null)return 1; return i.CompareTo(other.i); }
}
[Serializable]
public class KTimespan : IComparable
{
public TimeSpan t; private KTimespan() { }public KTimespan(long x) { t = new TimeSpan(x == Int64.MinValue ? Int64.MinValue : x / 100); }
public override string ToString() { return IsNull(t) ? "" : t.ToString(); }
public override bool Equals(object o) { if (o == null)return false; if (this.GetType() != o.GetType())return false; KTimespan n = (KTimespan)o; return t.Ticks == n.t.Ticks; }
public override int GetHashCode() { return t.GetHashCode(); }
public int CompareTo(object o) { if (o == null)return 1; KTimespan other = o as KTimespan; if (other == null)return 1; return t.CompareTo(other.t); }
}
}
public class KException : Exception
{
public KException() { }
public KException(string message) : base(message) { }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment