I was attempting to write a simple function that would use the RandomNumberGenerator class to return an array of Int16, Int32 or Int64 based on a generic argument.
However, no matter how I try to structure the code, I cannot seem to get past the illegal conversion from T [] to short/int/long [], nor the conversion from IntXX to T. Please see the two comments in the code below.
It seems I am missing a basic construct that would allow a way around this. Any thoughts?
public static void GenerateRandom<T> (T [] data, bool nonZeroOnly = false)
where T: struct, System.IComparable, System.IFormattable, System.IConvertible
{
int size = 0;
byte [] bytes = null;
if ((typeof(T) != typeof(byte)) && (typeof(T) != typeof(short)) && (typeof(T) != typeof(int)) && (typeof(T) != typeof(long)))
{
throw (new System.ArgumentException("This method only accepts types [Byte], [Int16], [Int32], or [Int64].", "<T>"));
}
if (typeof(T) == typeof(byte))
{
using (System.Security.Cryptography.RandomNumberGenerator generator = System.Security.Cryptography.RandomNumberGenerator.Create())
{
// Invalid cast (implicit or explicit) from T [] to byte [].
if (nonZeroOnly) { generator.GetNonZeroBytes(data); }
else { generator.GetBytes(data); }
}
}
else
{
size = System.Runtime.InteropServices.Marshal.SizeOf(typeof(T));
bytes = new byte [data.Length * size];
using (System.Security.Cryptography.RandomNumberGenerator generator = System.Security.Cryptography.RandomNumberGenerator.Create())
{
if (nonZeroOnly) { generator.GetNonZeroBytes((byte []) System.Convert.ChangeType(data, typeof(byte []))); }
else { generator.GetBytes((byte []) System.Convert.ChangeType(data, typeof(byte []))); }
}
using (System.IO.MemoryStream stream = new System.IO.MemoryStream(bytes))
{
using (System.IO.BinaryReader reader = new System.IO.BinaryReader(stream))
{
// Invalid cast (implicit or explicit) from short/int/long to T.
if (typeof(T) == typeof(short)) { for (int i=0; i<bytes.Length; i+=size) { data[i] = reader.ReadInt16(); } }
else if (typeof(T) == typeof(int)) { for (int i=0; i<bytes.Length; i+=size) { data[i] = reader.ReadInt32(); } }
else if (typeof(T) == typeof(long)) { for (int i=0; i<bytes.Length; i+=size) { data[i] = reader.ReadInt64(); } }
}
}
}
}
On a side note, is there a more efficient way of converting a byte [] to a IntXX [] without using a stream and binary reader?
You can do this, to get from int to T:
void Foo<T>(T[] data)
{
...
int v = r.Next(255); // limit to byte.max for simplicity
data[i] = (T) Convert.ChangeType(v, typeof(T));
}
I think you are trying to be clever here and in the process making things far too complex. Just write threee separate methods:
Int16[] GenerateRandomShorts()
Int32[] GenerateRandomInts()
Int64[] GenerateRandomLongs()
There is absolutely no reason to use generics here, and the problems you are facing are the result of swimming against the current.
Simply use non-generic overloads and let the compiler pick the one to use based on the type of the first argument:
public static void GenerateRandom(byte[] data, bool nonZeroOnly = false)
{
using (var generator = RandomNumberGenerator.Create())
{
if (nonZeroOnly) { generator.GetNonZeroBytes(data); }
else { generator.GetBytes(data); }
}
}
public static void GenerateRandom(short[] data, bool nonZeroOnly = false)
{
var size = sizeof(short);
var bytes = new byte[data.Length * size];
GenerateRandom(bytes, nonZeroOnly);
for (var i = 0; i < data.Length; ++i) {
data[i] = BitConverter.ToInt16(bytes, i * size);
}
}
Two more overloads like the last one would take care of int and long.
Related
EDIT
Apologies if the original unedited question is misleading.
This question is not asking how to remove Invalid XML Chars from a string, answers to that question would be better directed here.
I'm not asking you to review my code.
What I'm looking for in answers is, a function with the signature
string <YourName>(string input, Func<char, bool> check);
that will have performance similar or better than RemoveCharsBufferCopyBlackList. Ideally this function would be more generic and if possible simpler to read, but these requirements are secondary.
I recently wrote a function to strip invalid XML chars from a string. In my application the strings can be modestly long and the invalid chars occur rarely. This excerise got me thinking. What ways can this be done in safe managed c# and, which would offer the best performance for my scenario.
Here is my test program, I've subtituted the "valid XML predicate" for one the omits the char 'X'.
class Program
{
static void Main()
{
var attempts = new List<Func<string, Func<char, bool>, string>>
{
RemoveCharsLinqWhiteList,
RemoveCharsFindAllWhiteList,
RemoveCharsBufferCopyBlackList
}
const string GoodString = "1234567890abcdefgabcedefg";
const string BadString = "1234567890abcdefgXabcedefg";
const int Iterations = 100000;
var timer = new StopWatch();
var testSet = new List<string>(Iterations);
for (var i = 0; i < Iterations; i++)
{
if (i % 1000 == 0)
{
testSet.Add(BadString);
}
else
{
testSet.Add(GoodString);
}
}
foreach (var attempt in attempts)
{
//Check function works and JIT
if (attempt.Invoke(BadString, IsNotUpperX) != GoodString)
{
throw new ApplicationException("Broken Function");
}
if (attempt.Invoke(GoodString, IsNotUpperX) != GoodString)
{
throw new ApplicationException("Broken Function");
}
timer.Reset();
timer.Start();
foreach (var t in testSet)
{
attempt.Invoke(t, IsNotUpperX);
}
timer.Stop();
Console.WriteLine(
"{0} iterations of function \"{1}\" performed in {2}ms",
Iterations,
attempt.Method,
timer.ElapsedMilliseconds);
Console.WriteLine();
}
Console.Readkey();
}
private static bool IsNotUpperX(char value)
{
return value != 'X';
}
private static string RemoveCharsLinqWhiteList(string input,
Func<char, bool> check);
{
return new string(input.Where(check).ToArray());
}
private static string RemoveCharsFindAllWhiteList(string input,
Func<char, bool> check);
{
return new string(Array.FindAll(input.ToCharArray(), check.Invoke));
}
private static string RemoveCharsBufferCopyBlackList(string input,
Func<char, bool> check);
{
char[] inputArray = null;
char[] outputBuffer = null;
var blackCount = 0;
var lastb = -1;
var whitePos = 0;
for (var b = 0; b , input.Length; b++)
{
if (!check.invoke(input[b]))
{
var whites = b - lastb - 1;
if (whites > 0)
{
if (outputBuffer == null)
{
outputBuffer = new char[input.Length - blackCount];
}
if (inputArray == null)
{
inputArray = input.ToCharArray();
}
Buffer.BlockCopy(
inputArray,
(lastb + 1) * 2,
outputBuffer,
whitePos * 2,
whites * 2);
whitePos += whites;
}
lastb = b;
blackCount++;
}
}
if (blackCount == 0)
{
return input;
}
var remaining = inputArray.Length - 1 - lastb;
if (remaining > 0)
{
Buffer.BlockCopy(
inputArray,
(lastb + 1) * 2,
outputBuffer,
whitePos * 2,
remaining * 2);
}
return new string(outputBuffer, 0, inputArray.Length - blackCount);
}
}
If you run the attempts you'll note that the performance improves as the functions get more specialised. Is there a faster and more generic way to perform this operation? Or if there is no generic option is there a way that is just faster?
Please note that I am not actually interested in removing 'X' and in practice the predicate is more complicated.
You certainly don't want to use LINQ to Objects aka enumerators to do this if you require high performance. Also, don't invoke a delegate per char. Delegate invocations are costly compared to the actual operation you are doing.
RemoveCharsBufferCopyBlackList looks good (except for the delegate call per character).
I recommend that you inline the contents of the delegate hard-coded. Play around with different ways to write the condition. You may get better performance by first checking the current char against a range of known good chars (e.g. 0x20-0xFF) and if it matches let it through. This test will pass almost always so you can save the expensive checks against individual characters which are invalid in XML.
Edit: I just remembered I solved this problem a while ago:
static readonly string invalidXmlChars =
Enumerable.Range(0, 0x20)
.Where(i => !(i == '\u000A' || i == '\u000D' || i == '\u0009'))
.Select(i => (char)i)
.ConcatToString()
+ "\uFFFE\uFFFF";
public static string RemoveInvalidXmlChars(string str)
{
return RemoveInvalidXmlChars(str, false);
}
internal static string RemoveInvalidXmlChars(string str, bool forceRemoveSurrogates)
{
if (str == null) throw new ArgumentNullException("str");
if (!ContainsInvalidXmlChars(str, forceRemoveSurrogates))
return str;
str = str.RemoveCharset(invalidXmlChars);
if (forceRemoveSurrogates)
{
for (int i = 0; i < str.Length; i++)
{
if (IsSurrogate(str[i]))
{
str = str.Where(c => !IsSurrogate(c)).ConcatToString();
break;
}
}
}
return str;
}
static bool IsSurrogate(char c)
{
return c >= 0xD800 && c < 0xE000;
}
internal static bool ContainsInvalidXmlChars(string str)
{
return ContainsInvalidXmlChars(str, false);
}
public static bool ContainsInvalidXmlChars(string str, bool forceRemoveSurrogates)
{
if (str == null) throw new ArgumentNullException("str");
for (int i = 0; i < str.Length; i++)
{
if (str[i] < 0x20 && !(str[i] == '\u000A' || str[i] == '\u000D' || str[i] == '\u0009'))
return true;
if (str[i] >= 0xD800)
{
if (forceRemoveSurrogates && str[i] < 0xE000)
return true;
if ((str[i] == '\uFFFE' || str[i] == '\uFFFF'))
return true;
}
}
return false;
}
Notice, that RemoveInvalidXmlChars first invokes ContainsInvalidXmlChars to save the string allocation. Most strings do not contain invalid XML chars so we can be optimistic.
Ive got this problem:
Line 20 (LOOT_FromContainer(container) I need that to use as Invoke, because the process I want to exec takes some time, so ServerMessageHandler won't handle it...
If I just simply rewrite that line to LOOT_FromContainer.BeginInvoke(container); then I have this error:
error CS0119: 'LOOT.LOOT_FromContainer(Phoenix.Serial)' is a 'method',
which is not valid in the given context
I'm new to C#, came from PHP, and about Invoke I don't know much really. I've been trying to sort this out for a couple of days, not even google helped...
[ServerMessageHandler(0x3C)]
public CallbackResult ContainerContains(byte[] data, CallbackResult prevResult)
{
PacketReader reader = new PacketReader(data);
reader.Skip(3);
ushort len = reader.ReadUInt16();
for (int i = 0; i < len; i++)
{
Serial serial = (Serial)(reader.ReadUInt32());
ushort graphic = (ushort)(reader.ReadUInt16());
reader.Skip(7);
Serial container = (Serial)(reader.ReadUInt32());
ushort color = (ushort)(reader.ReadUInt16());
if (((int)graphic == 0x0E76) && ((int)color == 0x049A))
{
LOOT_FromContainer.BeginInvoke(container);
}
}
return CallbackResult.Normal;
}
[Command]
public static void LOOT_FromContainer(Serial target)
{
UOItem lootCorpse = new UOItem(target);
if (lootCorpse.Graphic == 0x2006)
{
if (((draw == 1) && (World.Player.Backpack.AllItems.Count(draw_knife[0], draw_knife[1]) > 0)) || (World.Player.Layers[Layer.RightHand].Exist))
{
if ((lootCorpse.Amount != 400) && (lootCorpse.Amount != 401))
{
if (draw == 0)
{
UO.WaitTargetObject(lootCorpse);
UO.UseObject(World.Player.Layers[Layer.RightHand].Serial);
}
else
{
UO.WaitTargetObject(lootCorpse);
UO.UseType(draw_knife[0], draw_knife[1]);
}
UO.Wait(500);
}
}
else
{
UO.Print("Neni cim rezat, pouze lootim");
}
for (int i = 0; i < loot.Length; i++)
{
if (lootCorpse.Items.Count(loot[i][0], loot[i][1]) > 0)
{
if (loot[i][2] == 1)
{
if (loot[i][4] == 1)
{
UO.MoveItem(lootCorpse.Items.FindType(loot[i][0], loot[i][1]), 0, Aliases.GetObject("loot_bag"), loot[i][5], loot[i][6]);
UO.Wait(200);
}
else
{
UO.MoveItem(lootCorpse.Items.FindType(loot[i][0], loot[i][1]), 0, World.Player.Backpack);
UO.Wait(200);
}
}
}
}
}
}
I think this is what you need. You need to declare a delegate with the same return type and input parameters as your method, instantiate this delegate pointing it at your method and then call BeginInvoke on it passing in your serial variable, followed by null, null:
public delegate void LFC(Serial target);
[ServerMessageHandler(0x3C)]
public CallbackResult ContainerContains(byte[] data, CallbackResult prevResult)
{
PacketReader reader = new PacketReader(data);
reader.Skip(3);
ushort len = reader.ReadUInt16();
for (int i = 0; i < len; i++)
{
Serial serial = (Serial)(reader.ReadUInt32());
ushort graphic = (ushort)(reader.ReadUInt16());
reader.Skip(7);
Serial container = (Serial)(reader.ReadUInt32());
ushort color = (ushort)(reader.ReadUInt16());
LFC = lootfromcontainer = new LFC(LOOT_FromContainer);
if (((int)graphic == 0x0E76) && ((int)color == 0x049A))
{
lootfromcontainer.BeginInvoke(container, null, null);
//LOOT_FromContainer.BeginInvoke(container);
}
}
return CallbackResult.Normal;
}
[Command]
public static void LOOT_FromContainer(Serial target)
{
UOItem lootCorpse = new UOItem(target);
if (lootCorpse.Graphic == 0x2006)
{
if (((draw == 1) && (World.Player.Backpack.AllItems.Count(draw_knife[0], draw_knife[1]) > 0)) || (World.Player.Layers[Layer.RightHand].Exist))
{
if ((lootCorpse.Amount != 400) && (lootCorpse.Amount != 401))
{
if (draw == 0)
{
UO.WaitTargetObject(lootCorpse);
UO.UseObject(World.Player.Layers[Layer.RightHand].Serial);
}
else
{
UO.WaitTargetObject(lootCorpse);
UO.UseType(draw_knife[0], draw_knife[1]);
}
UO.Wait(500);
}
}
else
{
UO.Print("Neni cim rezat, pouze lootim");
}
for (int i = 0; i < loot.Length; i++)
{
if (lootCorpse.Items.Count(loot[i][0], loot[i][1]) > 0)
{
if (loot[i][2] == 1)
{
if (loot[i][4] == 1)
{
UO.MoveItem(lootCorpse.Items.FindType(loot[i][0], loot[i][1]), 0, Aliases.GetObject("loot_bag"), loot[i][5], loot[i][6]);
UO.Wait(200);
}
else
{
UO.MoveItem(lootCorpse.Items.FindType(loot[i][0], loot[i][1]), 0, World.Player.Backpack);
UO.Wait(200);
}
}
}
}
}
}
If you simply need to run that in another thread you can use ThreadPool.
For this you would need few easy things:
Instead of:
LOOT_FromContainer.BeginInvoke(container);
You would use:
ThreadPool.QueueUserWorkItem(LOOT_FromContainer, container);
And slightly modify your LOOT_FromContainer method to:
public static void LOOT_FromContainer(object prm)
{
var target = (Serial)prm;
// ...
I'm assuming LOOT_FromCotainer is a control, and given the error your compiler returns, one can roughly guess the problem is that you're calling BeginInvoke with a non-delegate. BeginInvoke is called as follows:
LOOT_FromContainer.BeginInvoke(container); //where container is a delegate that maybe declared as follows
private delegate void container; //may also contain parameters eg container(string s);
So just rework your code to follow in this manner. See this document for example on how to use BeginInvoke.
Okay, This is going to be long.
First of all I want to introduce you what I do need:
I need suggestions, your opinions, the right method behind this server-client data sending & receiving (if I'm wrong at somepoint)
Need to fix points where I stuck (I've pointed them)
Firstly I've made a class which is actually a buffer and converter for sending & receiving data. I mean there is(was) methods like:
void Write(byte arg)
void Write(ushort arg)
void Write(DateTime arg)
void Write(DateTime[] arg)
byte ReadByte()
int[] ReadIntArray(int count)
etc. etc. This methods actually writes bytes to a List<byte> motherBuffer . And there is methods like Send() sends data via stream and Clear() clears the buffer.
public void Clear()
{
motherBuffer.Clear();
}
public void Send()
{
Stream.Write(motherBuffer.ToArray(), 0, motherBuffer.Count);
}
Firstly the logic of my class was, like this:
public void Write(byte arg)
{
motherBuffer.Add(arg)
}
public void Write(short arg)
{
foreach (byte b in BitConverter.GetBytes(arg)) // calls Write(byte) twice
Write(b);
}
public byte ReadByte()
{
if (!IsDataAvailable())
throw new Exception("No-data is available to be read.");
else
return (byte)Stream.ReadByte();
}
public short ReadShort()
{
return BitConverter.ToInt16(new byte[2] { ReadByte(), ReadByte() }, 0); // calls readbyte twice
}
public short[] ReadShortArray(int length)
{
short[] r = new short[length];
for (int i = 0; i <= length - 1; i++)
r[i] = ReadShort();
return r;
}
but after I've figured out there is a better method, I've changed this something like:
public void Write(byte arg)
{
motherBuffer.Add(arg);
}
public void Write(byte[] arg)
{
motherBuffer.AddRange(arg);
}
public void Write(short arg)
{
Write(BitConverter.GetBytes(arg)); // this time only calls Write(byte[]) once
}
public byte ReadByte()
{
if (!IsDataAvailable())
throw new Exception("No-data is available to be read.");
else
{
int read = Stream.ReadByte();
if (read < 0)
throw new Exception("End of stream -1");
else
return (byte)read;
}
}
public byte[] ReadByteArray(int count)
{
if (!IsDataAvailable())
throw new Exception("No-data is available to be read.");
else
{
byte[] read = new byte[count];
int result = Stream.Read(read, 0, count);
if (result <= 0)
throw new Exception("End of stream -1");
else
return read;
}
}
public short ReadShort()
{
return BitConverter.ToInt16(ReadByteArray(2), 0); // this time calls once too
}
public short[] ReadShortArray(int length)
{
short[] r = new short[length];
for (int i = 0; i <= length - 1; i++)
r[i] = ReadShort();
return r;
}
After All this, I thought why didn't I do something like (and really felt bad because This is more short way, also this is where I was going to stuck):
public T Read<T>() where T : int // I don't actually know what 'where T : int' does just tried for 'return (int)-1;'
{
if (!IsDataAvailable())
throw new Exception("No-data is available to read.");
if (typeof(T) == typeof(byte))
{
int read = Stream.ReadByte();
if (read < 0)
return (int)-1; // STUCK HERE
else
return read as T; // STUCK HERE
}
else
{
// STUCK HERE : because I don't know how to return & convert type of 'T'
}
}
public int ReadArray<T>(T[] buffer, int offset, int count)
{
if (!IsDataAvailable())
throw new Exception("No-data is available to read.");
if (typeof(T) == typeof(byte))
{
return Stream.Read(buffer as byte[], offset, count);
}
else
{
int m = 2; // multplier for example: this is short. I didn't figure out how to get it like sizeof(T)
byte[] byteBuffer = new byte[count * m]; // because I have to read twice amount of byte
ReadArray<byte>(byteBuffer, 0, byteBuffer.Length);
// I really don't know how could I use T with BitConverter.GetBytes( of T );
}
}
Do not use generics, they are your enemy in this case. Just code what you want and go forward. What you could really improve is to use WCF or Remoting for network communication instead of manual marshaling.
This it one way of implementing Read:
public static T Read<T>(this Stream stream)
{
object ret = null;
if (typeof(T) == typeof(byte))
ret = stream.ReadInt8();
else if (typeof(T) == typeof(Int16))
ret = stream.ReadInt16();
else if (typeof(T) == typeof(Int32))
ret = stream.ReadInt32();
if (ret == null)
throw new ArgumentException("Unable to read type - " + typeof(T));
return (T)ret;
}
public int ReadArray<T>(this Stream stream, T[] buffer, int offset, int count)
{
for (int i = offset; i< count; i++) buffer[i] = stream.Read<T>();
}
You might want to peek into WCF a bit, as it already solves this problem better than you will ever be able to, and will let you focus on your application logic directly without worrying on how to move data between tiers;
this is the shortest example of WCF I could find, hope it helps
I know this post is a year old but i thinks its worth adding to.
Ok first things first, as almost everybody else has said use WCF or other already implemented and tested system to communicate between tiers.
But to fill in your blanks:
where T : int is a generic constraint that says you will only return an int. If int was replace with a class likeStream then you are saying you will only return a Stream or class derived from Stream.
Because you have a generic constraint of int, typeof(T) will never equal typeof(byte).
The as operator can only be used on classes not structs. Because of the generic constraint of int, T is a struct.
I can tell you that to address the issue with return (int)-1; you would need to first cast to object then to T, like so return (T)(object)-1;. But I can’t really explain why as I don’t fully understand that aspect. I can point you in the direction of Covariance and Contravariance in Generics http://msdn.microsoft.com/en-us/library/dd799517.aspx.
I hope you find this helpful.
I have a array int[] numArray . I want to know is there any straight forward way to just check whether array has negative numbers in it ?
If there is no direct method even linq will do . I am bit new to linq . Can anyone suggest ?
If you're open to using LINQ:
var containsNegatives = numArray.Any(n => n < 0);
Or, if you want to do it the "old fashioned" way...you just have to loop:
var containsNegatives = false;
foreach(var n in numArray)
{
if(n < 0)
{
containsNegatives = true;
break;
}
}
And if you really want to get fancy, you could turn that into an Extension method:
public static class EnumerableExtensions
{
public static bool ContainsNegatives(this IEnumerable<int> numbers)
{
foreach(n in numbers)
{
if(n < 0) return true;
}
return false;
}
}
And call it from your code like:
var containsNegatives = numArray.ContainsNegatives();
You could use Any:
bool containsNegative = numArray.Any(i => i < 0)
Or
bool containsNegative = numArray.Min() < 0;
EDIT
int[] negativeNumbers = numArray.Where(i => i < 0).ToArray();
var negativeExist = numArray.Any(a => a < 0);
You can use Array.Find(T) method to perform this task.
public static T Find<T>(
T[] array,
Predicate<T> match
)
For example,
using System;
using System.Drawing;
public class Example
{
public static void Main()
{
// Create an array of five Point structures.
Point[] points = { new Point(100, 200),
new Point(150, 250), new Point(250, 375),
new Point(275, 395), new Point(295, 450) };
// To find the first Point structure for which X times Y
// is greater than 100000, pass the array and a delegate
// that represents the ProductGT10 method to the static
// Find method of the Array class.
Point first = Array.Find(points, ProductGT10);
// Note that you do not need to create the delegate
// explicitly, or to specify the type parameter of the
// generic method, because the C# compiler has enough
// context to determine that information for you.
// Display the first structure found.
Console.WriteLine("Found: X = {0}, Y = {1}", first.X, first.Y);
}
// This method implements the test condition for the Find
// method.
private static bool ProductGT10(Point p)
{
if (p.X * p.Y > 100000)
{
return true;
}
else
{
return false;
}
}
}
/* This code example produces the following output:
Found: X = 275, Y = 395
*/
Traditional:
foreach (int number in numArray) { if (number < 0) return true; }
return false;
With LINQ:
bool result = numArray.Any(x => x < 0);
A bit twidling version would be
public static bool AnyNegative(int[] arr){
const long firstBit = 2147483648;
var res = false;
for (var i = 0; i < arr.Length && !res; i++) res = (arr[i] & firstBit) == firstBit;
return res;
}
you can the call it like this>
int arr = {...}
if(arr.AnyNegative()){
//do stuf if there's any negative numbers
}
of course this is just an obfuscated version of
public static bool AnyNegative(int[] arr){
var res = false;
for (var i = 0; i < arr.Length && !res; i++) res = arr[i] < 0;
return res;
}
I am looking for fast class for to work with text files and comfortable reading different object (methods like NextInt32, NextDouble, NextLine, etc). Can you advice me something?
Edit: BinaryReader is bad class in my case. Format of my data is not binary. I have file like
1 2 3
FirstToken NextToken
1.23 2,34
And I want read this file with code like:
int a = FileReader.NextInt32();
int b = FileReader.NextInt32();
int c = FileReader.NextInt32();
int d = FileReader.NextString();
int e = FileReader.NextString();
int f = FileReader.NextDouble();
int g = FileReader.NextDouble();
Edit2: I am looking for analog Scanner from Java
I believe this extension method for TextReader would do the trick:
public static class TextReaderTokenizer
{
// Adjust as needed. -1 is EOF.
private static int[] whitespace = { -1, ' ', '\r' , '\n', '\t' };
public static T ReadToken<T>(this TextReader reader)
{
StringBuilder sb = new StringBuilder();
while (Array.IndexOf(whitespace, reader.Peek()) < 0)
{
sb.Append((char)reader.Read());
}
return (T)Convert.ChangeType(sb.ToString(), typeof(T));
}
}
It can be used thus:
TextReader reader = File.OpenText("foo.txt");
int n = reader.ReadToken<int>();
string s = reader.ReadToken<string>();
[EDIT] As requested in question comments, here's an instance wrapper version of the above that is parametrized with delimiters and CultureInfo:
public class TextTokenizer
{
private TextReader reader;
private Predicate<char> isDelim;
private CultureInfo cultureInfo;
public TextTokenizer(TextReader reader, Predicate<char> isDelim, CultureInfo cultureInfo)
{
this.reader = reader;
this.isDelim = isDelim;
this.cultureInfo = cultureInfo;
}
public TextTokenizer(TextReader reader, char[] delims, CultureInfo cultureInfo)
{
this.reader = reader;
this.isDelim = c => Array.IndexOf(delims, c) >= 0;
this.cultureInfo = cultureInfo;
}
public TextReader BaseReader
{
get { return reader; }
}
public T ReadToken<T>()
{
StringBuilder sb = new StringBuilder();
while (true)
{
int c = reader.Peek();
if (c < 0 || isDelim((char)c))
{
break;
}
sb.Append((char)reader.Read());
}
return (T)Convert.ChangeType(sb.ToString(), typeof(T));
}
}
Sample usage:
TextReader reader = File.OpenText("foo.txt");
TextTokenizer tokenizer = new TextTokenizer(
reader,
new[] { ' ', '\r', '\n', '\t' },
CultureInfo.InvariantCulture);
int n = tokenizer.ReadToken<int>();
string s = tokenizer.ReadToken<string>();
I'm going to add this as a separate answer because it's quite distinct from the answer I already gave. Here's how you could start creating your own Scanner class:
class Scanner : System.IO.StringReader
{
string currentWord;
public Scanner(string source) : base(source)
{
readNextWord();
}
private void ReadNextWord()
{
System.Text.StringBuilder sb = new StringBuilder();
char nextChar;
int next;
do
{
next = this.Read();
if (next < 0)
break;
nextChar = (char)next;
if (char.IsWhiteSpace(nextChar))
break;
sb.Append(nextChar);
} while (true);
while((this.Peek() >= 0) && (char.IsWhiteSpace((char)this.Peek())))
this.Read();
if (sb.Length > 0)
currentWord = sb.ToString();
else
currentWord = null;
}
public bool HasNextInt()
{
if (currentWord == null)
return false;
int dummy;
return int.TryParse(currentWord, out dummy);
}
public int NextInt()
{
try
{
return int.Parse(currentWord);
}
finally
{
readNextWord();
}
}
public bool HasNextDouble()
{
if (currentWord == null)
return false;
double dummy;
return double.TryParse(currentWord, out dummy);
}
public double NextDouble()
{
try
{
return double.Parse(currentWord);
}
finally
{
readNextWord();
}
}
public bool HasNext()
{
return currentWord != null;
}
}
You should define exactly what your file format is meant to look like. How would you represent a string with a space in it? What determines where the line terminators go?
In general you can use TextReader and its ReadLine method, followed by double.TryParse, int.TryParse etc - but you'll need to pin the format down more first.
Have you checked out the BinaryReader class? Yes it's a text file but there is nothing stopping you from treating it as binary data and hence using BinaryReader. It has all of the methods that you are looking for with the exception of ReadLine. However it wouldn't be too difficult to implement that method on top of BinaryReader.
If you do need text files (ie UTF-8 or ASCII encoding) then the binary writer will not work.
You can use the TextReader, but unlike the BinaryReader and the TextWriter it does not support any types other than Line and char. You will have to define what separators are allowed and parse the Line base data yourself.
The System.IO.BinaryReader class is what you need.
Example of implementation of a ReadLine method:
public static class Extensions
{
public static String ReadLine(this BinaryReader binaryReader)
{
var bytes = new List<Byte>();
byte temp;
while ((temp = (byte)binaryReader.Read()) < 10)
bytes.Add(temp);
return Encoding.Default.GetString(bytes.ToArray());
}
}
Example for using this class:
using System;
using System.IO;
using System.Security.Permissions;
class Test
{
static void Main()
{
// Load application settings.
AppSettings appSettings = new AppSettings();
Console.WriteLine("App settings:\nAspect Ratio: {0}, " +
"Lookup directory: {1},\nAuto save time: {2} minutes, " +
"Show status bar: {3}\n",
new Object[4]{appSettings.AspectRatio.ToString(),
appSettings.LookupDir, appSettings.AutoSaveTime.ToString(),
appSettings.ShowStatusBar.ToString()});
// Change the settings.
appSettings.AspectRatio = 1.250F;
appSettings.LookupDir = #"C:\Temp";
appSettings.AutoSaveTime = 10;
appSettings.ShowStatusBar = true;
// Save the new settings.
appSettings.Close();
}
}
// Store and retrieve application settings.
class AppSettings
{
const string fileName = "AppSettings####.dat";
float aspectRatio;
string lookupDir;
int autoSaveTime;
bool showStatusBar;
public float AspectRatio
{
get{ return aspectRatio; }
set{ aspectRatio = value; }
}
public string LookupDir
{
get{ return lookupDir; }
set{ lookupDir = value; }
}
public int AutoSaveTime
{
get{ return autoSaveTime; }
set{ autoSaveTime = value; }
}
public bool ShowStatusBar
{
get{ return showStatusBar; }
set{ showStatusBar = value; }
}
public AppSettings()
{
// Create default application settings.
aspectRatio = 1.3333F;
lookupDir = #"C:\AppDirectory";
autoSaveTime = 30;
showStatusBar = false;
if(File.Exists(fileName))
{
BinaryReader binReader =
new BinaryReader(File.Open(fileName, FileMode.Open));
try
{
// If the file is not empty,
// read the application settings.
// First read 4 bytes into a buffer to
// determine if the file is empty.
byte[] testArray = new byte[3];
int count = binReader.Read(testArray, 0, 3);
if (count != 0)
{
// Reset the position in the stream to zero.
binReader.BaseStream.Seek(0, SeekOrigin.Begin);
aspectRatio = binReader.ReadSingle();
lookupDir = binReader.ReadString();
autoSaveTime = binReader.ReadInt32();
showStatusBar = binReader.ReadBoolean();
}
}
// If the end of the stream is reached before reading
// the four data values, ignore the error and use the
// default settings for the remaining values.
catch(EndOfStreamException e)
{
Console.WriteLine("{0} caught and ignored. " +
"Using default values.", e.GetType().Name);
}
finally
{
binReader.Close();
}
}
}
// Create a file and store the application settings.
public void Close()
{
using(BinaryWriter binWriter =
new BinaryWriter(File.Open(fileName, FileMode.Create)))
{
binWriter.Write(aspectRatio);
binWriter.Write(lookupDir);
binWriter.Write(autoSaveTime);
binWriter.Write(showStatusBar);
}
}
}
You can probably use the System.IO.File Class to read the file and System.Convert to parse the strings you read from the file.
string line = String.Empty;
while( (line = file.ReadLine()).IsNullOrEmpty() == false )
{
TYPE value = Convert.ToTYPE( line );
}
Where TYPE is whatever type you're dealing with at that particular line / file.
If there are multiple values on one line you could do a split and read the individual values e.g.
string[] parts = line.Split(' ');
if( parts.Length > 1 )
{
foreach( string item in parts )
{
TYPE value = Convert.ToTYPE( item );
}
}
else
{
// Use the code from before
}