【问题标题】:printf-like function for C#C# 的类似 printf 的函数
【发布时间】:2018-11-09 11:48:00
【问题描述】:

我想为 C# 编写函数,它类似于 C 中的 sprintf(3) 和 vsnprintf(3) 函数(或类似于 Tcl 中的“格式”命令)。目前我已经取得了一些成功,但仍有一些问题。

以下是源代码。 “格式”类包含很少的公共函数,它接受可变参数列表(但不超过 7 个参数,见下文)并生成格式化字符串。这是 sprintf(3) 函数的模拟。函数“格式”也可以接受 C# 元组,它应该在第一个元素中保存格式字符串,在其他元素中保存格式字符串的参数——这类似于 vsprintf(3) 函数。

不幸的是,这里我有两个主要限制:

1) 我不能将超过七个参数传递给 format() 函数,因为参数是在 C# tuple 中传递的,并且 tuple 不能有超过八个元素(第一个元素是格式字符串本身,这是必需的,因为空元组在 C# 中不可用)。我希望获得一些建议,我该如何改进我的 format() 函数来避免这个限制。

2) 另一个主要限制是,并非所有类型都可以传递给 C# 模板。特别是,我无法将指针传递给格式化函数(在这种情况下,我会收到以下错误:“错误 CS0306:类型 'int*' 可能不能用作类型参数”)。这是 C# 限制?是否可以重写 format() 函数来避免这种限制?

另外一个主要的不便,就是我应该打开一些特定的库名,并且使用C函数名,这对于不同的操作系统是不同的:

  • 对于 Windows,我应该使用“msvcrt.dll”和“_snprintf”;

  • 对于 linux,我应该使用“libc.so.6”和“snprintf”;

  • 对于嵌入式平台上的 linux,C 库名称可能有不同的名称...

可以定义应该在运行时打开哪个库,或者外部函数编组可能仅在编译时确定?所以,我在这里看到了两种可能的变体:

1) 我需要为不同的目标平台生成不同的 DLL;

2) 或者我可以在运行时决定应该使用哪个函数名和 libc-library?

我不明白,如何重写第一个或第二个变体的代码。此外,了解 libc 名称看起来也很不方便。为什么不调用 dlsym("snprintf") ?

另一个问题,我尝试限制使用动态内存,但看起来不可能避免在堆上分配几个 String 和 StringBuilder 类。这看起来很可怕,因为在 C/C++ 上编程时,几乎可以在没有动态内存分配的情况下完成所有工作。可能有人建议我,如何改进 format() 函数以避免动态内存分配。

using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

namespace FK0
{
    using Args = ITuple;

    public class Format
    {
        // const string LIBC = "msvcrt.dll"; //"libc.so";
    // const string FUNC = "_snprintf";
        const string LIBC = "libc.so.6";
    const string FUNC = "snprintf";
        [DllImport(LIBC, EntryPoint=FUNC)] static private extern int snprintf(StringBuilder result, [MarshalAs(UnmanagedType.SysInt)] IntPtr size, StringBuilder format, [MarshalAs(UnmanagedType.I4)] int a1);
        [DllImport(LIBC, EntryPoint=FUNC)] static private extern int snprintf(StringBuilder result, [MarshalAs(UnmanagedType.SysInt)] IntPtr size, StringBuilder format, [MarshalAs(UnmanagedType.I8)] long a1);
        [DllImport(LIBC, EntryPoint=FUNC)] static private extern int snprintf(StringBuilder result, [MarshalAs(UnmanagedType.SysInt)] IntPtr size, StringBuilder format, double a1);
        [DllImport(LIBC, EntryPoint=FUNC)] static private extern int snprintf(StringBuilder result, [MarshalAs(UnmanagedType.SysInt)] IntPtr size, StringBuilder format, [MarshalAs(UnmanagedType.LPStr)]string a1);

        // function returns length of next format segment (string, copied as is, or single format spec.)
        static private int parse_format(string fmt, int pos)
        {
            int p = fmt.IndexOf('%', pos);
            if (p == -1) return fmt.Length - pos; // copy to end of string
            else if (p != pos) return p - pos;  // copy till %

            char[] fmt_term = {'d','i','o','u','x','X','e','E','f','F','g','G','a','A','c','s','p','n','%' };
            int e = fmt.IndexOfAny(fmt_term, p + 1);
            if (e == -1) throw new System.ArgumentException("invalid format string");
            return e - p + 1;  // format specifier length
        }

    // call real `snprintf(3)' from C-library, marshal arguments appropriately
        static private int call_snprintf(ref StringBuilder res, int len, StringBuilder fmt, Object arg)
        {
            if (arg is long || arg is ulong)
                return snprintf(res, (IntPtr)len, fmt, Convert.ToInt64(arg));
            else if (arg is float || arg is double || arg is decimal)
                return snprintf(res, (IntPtr)len, fmt, Convert.ToDouble(arg));
            else if (arg is string || arg is StringBuilder)
                return snprintf(res, (IntPtr)len, fmt, Convert.ToString(arg));
            else if (arg.GetType().IsPointer || arg is IntPtr)  // XXX can't pass pointer to template!!!
                return snprintf(res, (IntPtr)len, fmt, ((IntPtr)arg).ToInt64());
        //else if (arg.GetType()
            else
                return snprintf(res, (IntPtr)len, fmt, Convert.ToInt32(arg));
        }

    // vsnprintf-like function (accepts all arguments in tuple)
        static public string format(Args args)
        {
            if (! (args[0] is string))  // check, that first argument is string
                throw new System.ArgumentException("wrong string format type");

        // first pass
        // compute number of arguments, size of output string and max size of formatted output
            string fmt = args[0].ToString();
            StringBuilder ns = null, fs = new StringBuilder();
            int total_len = 0, maxlen = 0, narg = 1;
            int pos = 0;
            while (pos < fmt.Length) {
                int len = parse_format(fmt, pos);
                if (fmt[pos] == '%') { // pass format specifier to snprintf(3)
                    fs.Clear(); fs.Append(fmt, pos, len);
                    int flen = call_snprintf(ref ns, 0, fs, args[narg]);
                    if (flen == -1) throw new System.ArgumentException("wrong format string");
                    total_len += flen;
                    if (flen > maxlen) maxlen = flen;
                    narg++;
                }
                else { // compute size of literal part
                    total_len += len;
                }
                pos += len;
            }

            if (narg != args.Length)
                throw new System.ArgumentException("incorrect # of arguments for format string");

        // second pass
        // print each argument separately
            var result = new StringBuilder(total_len);
            var part = new StringBuilder(maxlen + 1);  // include terminating zero
            pos = 0; narg = 1;
            while (pos < fmt.Length) {
                int len = parse_format(fmt, pos);
                if (fmt[pos] == '%') { // pass format specifier to snprintf(3)
                    fs.Clear(); fs.Append(fmt, pos, len);
                    call_snprintf(ref part, part.Capacity, fs, args[narg++]);
                    result.Append(part);
            Console.WriteLine(part);
                }
                else { // copy literal part as is
                    result.Append(fmt, pos, len);
                }
        pos += len;
            }

            return result.ToString();
        }

    // C# have no vararg templates, as C++03, also max size of tuple limited to 8 elements,
    // also impossible to create empty tuple, so maximum number arguments limited to 7 (plus format string as 0-th element).

        static public string format<T1, T2, T3, T4, T5, T6, T7>(string fmt, T1 a1, T2 a2, T3 a3, T4 a4, T5 a5, T6 a6, T7 a7)
        {
            return format(Tuple.Create(fmt, a1, a2, a3, a4, a5, a6, a7));
        }

        static public string format<T1, T2, T3, T4, T5, T6>(string fmt, T1 a1, T2 a2, T3 a3, T4 a4, T5 a5, T6 a6)
        {
            return format(Tuple.Create(fmt, a1, a2, a3, a4, a5, a6));
        }

        static public string format<T1, T2, T3, T4, T5>(string fmt, T1 a1, T2 a2, T3 a3, T4 a4, T5 a5)
        {
            return format(Tuple.Create(fmt, a1, a2, a3, a4, a5));
        }

        static public string format<T1, T2, T3, T4>(string fmt, T1 a1, T2 a2, T3 a3, T4 a4)
        {
            return format(Tuple.Create(fmt, a1, a2, a3, a4));
        }

        static public string format<T1, T2, T3>(string fmt, T1 a1, T2 a2, T3 a3)
        {
            return format(Tuple.Create(fmt, a1, a2, a3));
        }

        static public string format<T1, T2>(string fmt, T1 a1, T2 a2)
        {
            return format(Tuple.Create(fmt, a1, a2));
        }

        static public string format<T1>(string fmt, T1 a1)
        {
            return format(Tuple.Create(fmt, a1));
        }

        static public string format(string fmt)
        {
            return format(Tuple.Create(fmt));
        }
    };
}

public class Program
{
    unsafe public static void Main()
    {
    //  System.Threading.Thread.Sleep(100000);
        int z = 123;
        int* y = &z;
        IntPtr v = (IntPtr)y;
        string s = FK0.Format.format("%p %d %d", v, *y, z);
        Console.WriteLine(s);
    }
}

【问题讨论】:

  • 您是否有特殊原因要使用它而不是现有的 C# 功能,如 string.Format
  • 正如@mjwills 所说,除了可以通过string.Format 调用轻松完成的大量工作之外,您还可以随时创建public class Tuple&lt;T1, T2, ....., T25&gt;。不过,这并不能解决问题
  • 还有一个(未记录的?)关键字__arglist,可用于调用像printf 这样的可变参数函数。但对于生产环境中的代码,我建议坚持使用 String.Format
  • 这里有大约六个问题,整个企业一开始似乎是个坏主意;如果要调用 printf,只需调用 printf 的 C++/CLI 版本即可!您能否将其分解为每个问题一个简单的问题,其中每个问题都有一个明确、具体的答案?

标签: c# .net printf clr coreclr


【解决方案1】:

使用 C# params 参数修饰符,您可以将参数作为数组传递给方法:

public static string format(string format, params object[] args) {
    var n = 0;
    return String.Format(format.Replace(new Regex(@"%[+-0-9.]*[a-z]"), m => $"{{{n++}}}"), args);
}

现在你可以这样称呼它:

Console.WriteLine(Format.format("test: %s %x", "this", 23));

当然,这个版本的format 只是以默认格式转储所有参数,您需要在实际实现中处理每个格式说明符。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-02-05
    • 1970-01-01
    • 1970-01-01
    • 2013-02-04
    • 1970-01-01
    • 1970-01-01
    • 2020-01-09
    • 2012-12-27
    相关资源
    最近更新 更多