35

I used to code in C language in the past and I found the scanf function very useful. Unfortunately, there is no equivalent in C#.

I am using using it to parse semi-structured text files.

I found an interresting example of scanf implementation here. Unfortunately, it looks old and incomplete.

Does anyone know a scanf C# implementation ? Or at least something that would work as a reversed string.Format?

I82Much
  • 26,901
  • 13
  • 88
  • 119
Larry
  • 17,605
  • 9
  • 77
  • 106

10 Answers10

9

Since the files are "semi-structured" can't you use a combination of ReadLine() and TryParse() methods, or the Regex class to parse your data?

Sabuncu
  • 5,095
  • 5
  • 55
  • 89
Mitch Wheat
  • 295,962
  • 43
  • 465
  • 541
  • Sure I could :) However, scanf is so confortable and handy compared to regex. I am pretty sure it worth the effort. – Larry Jan 23 '09 at 07:57
  • 2
    Regex is not so bad. Download one of the free tools (like Expresso) and regexes are mu7ch easier to create and use. – Mitch Wheat Jan 23 '09 at 07:59
  • Personally I hate scanf(): regexes give you so much more control & flexibility, it's worth learning the basics at least. I learned regexes in my Perl programming days & loved them, so I was overjoyed to find Regex in .NET. – AAT Sep 11 '09 at 10:46
  • 1
    I am currently using a SScanf() open source in this link... try it out http://www.blackbeltcoder.com/Articles/strings/a-sscanf-replacement-for-net – Dhanasekar Murugesan Oct 31 '12 at 12:20
  • 1
    Funny that noone questions the performance of regex vs scanf here. – Ray Feb 08 '15 at 21:53
8

If regular expressions aren't working for you, I've just posted a sscanf() replacement for .NET. The code can be viewed and downloaded at http://www.blackbeltcoder.com/Articles/strings/a-sscanf-replacement-for-net.

Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • That is an evil hack imho, you had better use regex group matching. This is the .net equivalent of sscanf and even better. See post below. – jurik Jun 18 '14 at 14:14
  • 2
    @jurik: I suppose it would be too much to ask you to back up your statement? Regex is great, and you can use it if you like. But how is implementing scanf in C# a "hack"? That's just being critical about something you don't like. That doesn't make it a hack. For goodness sake, have a little tolerance for people who might decide to do something differently than you like to do it. – Jonathan Wood Jun 18 '14 at 15:09
  • ok i might have come in a little strong without decent proof, but let me try to exlain. I have found this thread some time ago and after looking at the code I did not want to use it in production. It somehow felt like if the .net platform wanted to use scanf, it would have incorporated it. So i looked further and found the example i needed. It is the exact same functionality as sscanf but without custom code. I did not mean to offend you or your solution, i am sorry. I actually like hacks very much. Sometime it is necescary. – jurik Jun 19 '14 at 06:28
  • @jurik: RegEx does much more, so I can certainly understand people preferring it. The main reason I wrote scanf for C# was to make it easier for developers porting C code, and for people familiar with scanf syntax but not familiar with regex. – Jonathan Wood Jun 19 '14 at 13:54
  • @jonahthan, in that case. Nice one! porting is so much easier if at least the basics are covered. – jurik Jun 20 '14 at 10:59
5

I have found a better solution than using sscanf from C or some rewritten part by someone (no offence)

http://msdn.microsoft.com/en-us/library/63ew9az0.aspx have a look at this article, it explains how to make named groups to extract the wanted data from a patterned string. Beware of the little error in the article and the better version below. (the colon was not part of the group)

using System;
using System.Text.RegularExpressions;

public class Example
{
   public static void Main()
   {
      string url = "http://www.contoso.com:8080/letters/readme.html";
      Regex r = new Regex(@"^(?<proto>\w+)://[^/]+?(?<port>:\d+)?/",RegexOptions.None, TimeSpan.FromMilliseconds(150));
      Match m = r.Match(url);
      if (m.Success)
         Console.WriteLine(r.Match(url).Result("${proto}:${port}")); 
   }
}
// The example displays the following output: 
//       http::8080
jurik
  • 126
  • 1
  • 6
  • 1
    Worth noting that this example does not actually give the intended output. It results in `http::8080`, when I test it. – Khale_Kitha Mar 07 '16 at 16:59
  • You are absolutely right, but it makes no sense. The colon is outside of the matched group, so it shouldn't be taken in. I assumed that proto was http and port was 8080 without colons. That is why i edited the original example. – jurik Apr 03 '17 at 09:00
5

You can use scanf directly from C runtime libraries, but this can be difficult if you need to run it with different parameters count. I recommend you to regular expressions for you task or describe that task here, maybe there is another ways.

okutane
  • 13,754
  • 10
  • 59
  • 67
  • Ow, I have no clue about how to use directly C runtime libraries. I'd rather avoid it and stick to regexes instead. – Larry Jan 23 '09 at 08:18
4

There is good reason why a scanf like function is missing from c#. It's very prone to error, and not flexible. Using Regex is much more flexible and powerful.

Another benefit is that it's easier to reuse throughout your code if you need to parse the same thing in different parts of the code.

sprite
  • 3,724
  • 3
  • 28
  • 30
4

Try importing msvcrt.dll

using System.Runtime.InteropServices;

namespace Sample
{
    class Program
    {
        [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int printf(string format, __arglist);

        [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int scanf(string format, __arglist);

        static void Main()
        {
            int a, b;
            scanf("%d%d", __arglist(out a, out b));
            printf("The sum of %d and %d is %d.\n", __arglist(a, b, a + b));
        }
    }
}

which works well on .NET Framework. But on Mono, it shows the error message:

Unhandled Exception:
System.InvalidProgramException: Invalid IL code in Sample.Program:Main (): IL_0009: call      0x0a000001


[ERROR] FATAL UNHANDLED EXCEPTION: System.InvalidProgramException: Invalid IL code in Sample.Program:Main (): IL_0009: call      0x0a000001

If you need Mono compatibility, you need to avoid using arglist

using System.Runtime.InteropServices;

namespace Sample
{
    class Program
    {
        [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int printf(string format, int a, int b, int c);

        [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int scanf(string format, out int a, out int b);

        static void Main()
        {
            int a, b;
            scanf("%d%d", out a, out b);
            printf("The sum of %d and %d is %d.\n", a, b, a + b);
        }
    }
}

in which the number of arguments is fixed.

Edit 2018-5-24

arglist doesn't work on .NET Core either. It seems that calling the C vararg function is deprecated. You should use the .NET string API such as String.Format instead.

Jason Lee
  • 502
  • 6
  • 15
2

I think you want the C# library functions either Parse or Convert.

// here's an example of getting the hex value from a command line 
// program.exe 0x00080000

static void Main(string[] args)
{
    int value = Convert.ToInt32(args[1].Substring(2), 16);
    Console.Out.WriteLine("Value is: " + value.ToString());
}
sth
  • 222,467
  • 53
  • 283
  • 367
0

You could use System.IO.FileStream, and System.IO.StreamReader, and then parse from there.

SMB
  • 633
  • 4
  • 7
0

Here is a basic version in plain C#:

    public static ArrayList scan(string s, string fmt)
    {
        ArrayList result = new ArrayList();

        int ind = 0; // s upto ind has been consumed

        for (int i = 0; i < fmt.Length; i++)
        {
            char c = fmt[i];
            if (c == '%' && i < fmt.Length - 1)
            {
                char d = fmt[i + 1];
                if (d == 's')
                {
                    string schars = ""; 
                    for (int j = ind; j < s.Length; j++)
                    {  if (Char.IsWhiteSpace(s[j]))
                       { break; }
                       else 
                       { schars = schars + s[j]; }
                    }
                    result.Add(schars);
                    ind = ind + schars.Length; 
                    i++;
                }
                else if (d == 'f')
                {
                    String fchars = "";
                    for (int j = ind; j < s.Length; j++)
                    {
                        Char x = s[j];
                        if (x == '.' || Char.IsDigit(x))
                        { fchars = fchars + x; }
                        else
                        { break; }
                    }

                    try
                    {
                        double v = double.Parse(fchars);
                        ind = ind + fchars.Length;
                        result.Add(v);
                    }
                    catch (Exception _ex)
                    { Console.WriteLine("!! Error in double format: " + fchars);  }
                    i++;
                }
                else if (d == 'd')
                {
                    String inchars = "";
                    for (int j = ind; j < s.Length; j++)
                    {
                        Char x = s[j];
                        if (Char.IsDigit(x))
                        { inchars = inchars + x; }
                        else
                        { break; }
                    }

                    try
                    {
                        int v = int.Parse(inchars);
                        ind = ind + inchars.Length;
                        result.Add(v);
                    }
                    catch (Exception _ex)
                    { Console.WriteLine("!! Error in integer format: " + inchars); }
                    i++;
                }
            }
            else if (s[ind] == c)
            { ind++; }
            else
            { return result; }

        }
        return result;
    }
-3

Although this looks sort of crude it does get the job done:

// Create the stream reader
sr = new StreamReader("myFile.txt");

// Read it
srRec = sr.ReadLine();

// Replace multiple spaces with one space
String rec1 = srRec.Replace("          ", " ");
String rec2 = rec1.Replace("         ", " ");
String rec3 = rec2.Replace("        ", " ");
String rec4 = rec3.Replace("       ", " ");
String rec5 = rec4.Replace("      ", " ");
String rec6 = rec5.Replace("     ", " ");
String rec7 = rec6.Replace("    ", " ");
String rec8 = rec7.Replace("   ", " ");
String rec9 = rec8.Replace("  ", " ");

// Finally split the string into an array of strings with Split
String[] srVals = rec9.Split(' ');

You can then use the array srVals as individual variables from the record.

  • 2
    Ugh. You have ten lines which could be replaced by one `var srVals = srRec.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);` for more predictable behaviour. But that aside, this doesn't really answer the question. – Peter Taylor May 04 '16 at 14:01