C#中的CSV文件读写


项目中经常遇到CSV文件的读写需求,其中的难点主要是CSV文件的解析。本文会介绍CsvHelperTextFieldParser正则表达式三种解析CSV文件的方法,顺带也会介绍一下CSV文件的写方法。

CSV文件标准

在介绍CSV文件的读写方法前,我们需要了解一下CSV文件的格式。

文件示例

一个简单的CSV文件:

Test1,Test2,Test3,Test4,Test5,Test6
str1,str2,str3,str4,str5,str6
str1,str2,str3,str4,str5,str6

一个不简单的CSV文件:

"Test1
"",""","Test2
"",""","Test3
"",""","Test4
"",""","Test5
"",""","Test6
"","""
" 中文,D23 ","3DFD4234""""""1232""1S2","ASD1"",""23,,,,213
23F32","
",,asd
" 中文,D23 ","3DFD4234""""""1232""1S2","ASD1"",""23,,,,213
23F32","
",,asd

你没看错,上面两个都是CSV文件,都只有3行CSV数据。第二个文件多看一眼都是精神污染,但项目中无法避免会出现这种文件。

RFC 4180

CSV文件没有官方的标准,但一般项目都会遵守 RFC 4180 标准。这是一个非官方的标准,内容如下:

  1. Each record is located on a separate line, delimited by a line break (CRLF).
  2. The last record in the file may or may not have an ending line break.
  3. There maybe an optional header line appearing as the first line of the file with the same format as normal record lines. This header will contain names corresponding to the fields in the file and should contain the same number of fields as the records in the rest of the file (the presence or absence of the header line should be indicated via the optional "header" parameter of this MIME type).
  4. Within the header and each record, there may be one or more fields, separated by commas. Each line should contain the same number of fields throughout the file. Spaces are considered part of a field and should not be ignored. The last field in the record must not be followed by a comma.
  5. Each field may or may not be enclosed in double quotes (however some programs, such as Microsoft Excel, do not use double quotes at all). If fields are not enclosed with double quotes, then double quotes may not appear inside the fields.
  6. Fields containing line breaks (CRLF), double quotes, and commas should be enclosed in double-quotes.
  7. If double-quotes are used to enclose fields, then a double-quote appearing inside a field must be escaped by preceding it with another double quote.

翻译一下:

  1. 每条记录位于单独的行上,由换行符 (CRLF) 分隔。
  2. 文件中的最后一条记录可能有也可能没有结束换行符。
  3. 可能有一个可选的标题行出现在文件的第一行,格式与普通记录行相同。此标题将包含与文件中的字段对应的名称,并且应包含与文件其余部分中的记录相同数量的字段(标题行的存在或不存在应通过此 MIME 类型的可选“标头”参数指示)。
  4. 在标题和每条记录中,可能有一个或多个字段,以逗号分隔。在整个文件中,每行应包含相同数量的字段。空格被视为字段的一部分,不应忽略。记录中的最后一个字段后面不能有逗号。
  5. 每个字段可以用双引号括起来,也可以不用双引号(但是某些程序,例如 Microsoft Excel,根本不使用双引号)。如果字段没有用双引号括起来,那么双引号可能不会出现在字段内。
  6. 包含换行符 (CRLF)、双引号和逗号的字段应该用双引号括起来。
  7. 如果使用双引号将字段括起来,则出现在字段中的双引号必须在其前面加上另一个双引号。

简化标准

上面的标准可能比较拗口,我们对它进行一些简化。要注意一下,简化不是简单的删减规则,而是将类似的类似进行合并便于理解。
后面的代码也会使用简化标准,简化标准如下:

  1. 每条记录位于单独的行上,由换行符 (CRLF) 分隔。
    注:此处的行不是普通文本意义上的行,是指符合CSV文件格式的一条记录(后面简称为CSV行),在文本上可能占据多行。

  2. 文件中的最后一条记录需有结束换行符,文件的第一行为标题行(标题行包含字段对应的名称,标题数与记录的字段数相同)。
    注:原标准中可有可无的选项统一规定为必须有,方便后期的解析,而且没有标题行让别人怎么看数据。

  3. 在标题和每条记录中,可能有一个或多个字段,以逗号分隔。在整个文件中,每行应包含相同数量的字段空格被视为字段的一部分,不应忽略。记录中的最后一个字段后面不能有逗号
    注:此标准未做简化,虽然也有其它标准使用空格、制表符等做分割的,但不使用逗号分割的文件还叫逗号分隔值文件吗。

  4. 每个字段都用双引号括起来,出现在字段中的双引号必须在其前面加上另一个双引号
    注:原标准有必须使用双引号和可选双引号的情况,那全部使用双引号肯定不会出错。*

读写CSV文件

在正式读写CSV文件前,我们需要先定义一个用于测试的Test类。代码如下:

class Test
{
    public string Test1{get;set;}
    public string Test2 { get; set; }
    public string Test3 { get; set; }
    public string Test4 { get; set; }
    public string Test5 { get; set; }
    public string Test6 { get; set; }

    //Parse方法会在自定义读写CSV文件时用到
    public static Test Parse (string[]fields )
    {
        try
        {
            Test ret = new Test();
            ret.Test1 = fields[0];
            ret.Test2 = fields[1];
            ret.Test3 = fields[2];
            ret.Test4 = fields[3];
            ret.Test5 = fields[4];
            ret.Test6 = fields[5];
            return ret;
        }
        catch (Exception)
        {
            //做一些异常处理,写日志之类的
            return null;
        }
    }
}

生成一些测试数据,代码如下:

static void Main(string[] args)
{
    //文件保存路径
    string path = "tset.csv";
    //清理之前的测试文件
    File.Delete("tset.csv");
      
    Test test = new Test();
    test.Test1 = " 中文,D23 ";
    test.Test2 = "3DFD4234\"\"\"1232\"1S2";
    test.Test3 = "ASD1\",\"23,,,,213\r23F32";
    test.Test4 = "\r";
    test.Test5 = string.Empty;
    test.Test6 = "asd";

    //测试数据
    var records = new List<Test> { test, test };

    //写CSV文件
    /*
    *直接把后面的写CSV文件代码复制到此处
    */

    //读CSV文件
     /*
    *直接把后面的读CSV文件代码复制到此处
    */
   
    Console.ReadLine();
}

使用CsvHelper

CsvHelper 是用于读取和写入 CSV 文件的库,支持自定义类对象的读写。
github上标星最高的CSV文件读写C#库,使用MS-PLApache 2.0开源协议。
使用NuGet下载CsvHelper,读写CSV文件的代码如下:

 //写CSV文件
using (var writer = new StreamWriter(path))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
    csv.WriteRecords(records);
}

using (var writer = new StreamWriter(path,true))
using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
    //追加
    foreach (var record in records)
    {
        csv.WriteRecord(record);
    }
}

//读CSV文件
using (var reader = new StreamReader(path))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
    records = csv.GetRecords<Test>().ToList();
    //逐行读取
    //records.Add(csv.GetRecord<Test>());
}

如果你只想要拿来就能用的库,那文章基本上到这里就结束了。

使用自定义方法

为了与CsvHelper区分,新建一个CsvFile类存放自定义读写CSV文件的代码,最后会提供类的完整源码。CsvFile类定义如下:

/// <summary>
/// CSV文件读写工具类
/// </summary>
public class CsvFile
{
    #region 写CSV文件
    //具体代码...
    #endregion

    #region 读CSV文件(使用TextFieldParser)
    //具体代码...
    #endregion

    #region 读CSV文件(使用正则表达式)
    //具体代码...
    #endregion

}

基于简化标准的写CSV文件

根据简化标准(具体标准内容见前文),写CSV文件代码如下:

#region 写CSV文件
//字段数组转为CSV记录行
private static string FieldsToLine(IEnumerable<string> fields)
{
    if (fields == null) return string.Empty;
    fields = fields.Select(field =>
    {
        if (field == null) field = string.Empty;
        //简化标准,所有字段都加双引号
        field = string.Format("\"{0}\"", field.Replace("\"", "\"\""));

        //不简化标准
        //field = field.Replace("\"", "\"\"");
        //if (field.IndexOfAny(new char[] { ',', '"', ' ', '\r' }) != -1)
        //{
        //    field = string.Format("\"{0}\"", field);
        //}
        return field;
    });
    string line = string.Format("{0}{1}", string.Join(",", fields), Environment.NewLine);
    return line;
}

//默认的字段转换方法
private static IEnumerable<string> GetObjFields<T>(T obj, bool isTitle) where T : class
{
    IEnumerable<string> fields;
    if (isTitle)
    {
        fields = obj.GetType().GetProperties().Select(pro => pro.Name);
    }
    else
    {
        fields = obj.GetType().GetProperties().Select(pro => pro.GetValue(obj)?.ToString());
    }
    return fields;
}

/// <summary>
/// 写CSV文件,默认第一行为标题
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list">数据列表</param>
/// <param name="path">文件路径</param>
/// <param name="append">追加记录</param>
/// <param name="func">字段转换方法</param>
/// <param name="defaultEncoding"></param>
public static void Write<T>(List<T> list, string path,bool append=true, Func<T, bool, IEnumerable<string>> func = null, Encoding defaultEncoding = null) where T : class
{
    if (list == null || list.Count == 0) return;
    if (defaultEncoding == null)
    {
        defaultEncoding = Encoding.UTF8;
    }
    if (func == null)
    {
        func = GetObjFields;
    }
    if (!File.Exists(path)|| !append)
    {
        var fields = func(list[0], true);
        string title = FieldsToLine(fields);
        File.WriteAllText(path, title, defaultEncoding);
    }
    using (StreamWriter sw = new StreamWriter(path, true, defaultEncoding))
    {
        list.ForEach(obj =>
        {
            var fields = func(obj, false);
            string line = FieldsToLine(fields);
            sw.Write(line);
        });
    }
}
#endregion

使用时,代码如下:

//写CSV文件
//使用自定义的字段转换方法,也是文章开头复杂CSV文件使用字段转换方法
CsvFile.Write(records, path, true, new Func<Test, bool, IEnumerable<string>>((obj, isTitle) =>
{
    IEnumerable<string> fields;
    if (isTitle)
    {
        fields = obj.GetType().GetProperties().Select(pro => pro.Name + Environment.NewLine + "\",\"");
    }
    else
    {
        fields = obj.GetType().GetProperties().Select(pro => pro.GetValue(obj)?.ToString());
    }
    return fields;
}));

//使用默认的字段转换方法
//CsvFile.Write(records, path);

你也可以使用默认的字段转换方法,代码如下:

CsvFile.Save(records, path);

使用TextFieldParser解析CSV文件

TextFieldParser是VB中解析CSV文件的类,C#虽然没有类似功能的类,不过可以调用VB的TextFieldParser来实现功能。
TextFieldParser解析CSV文件的代码如下:

#region 读CSV文件(使用TextFieldParser)
/// <summary>
/// 读CSV文件,默认第一行为标题
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="path">文件路径</param>
/// <param name="func">字段解析规则</param>
/// <param name="defaultEncoding">文件编码</param>
/// <returns></returns>
public static List<T> Read<T>(string path, Func<string[], T> func, Encoding defaultEncoding = null) where T : class
{
    if (defaultEncoding == null)
    {
        defaultEncoding = Encoding.UTF8;
    }
    List<T> list = new List<T>();
    using (TextFieldParser parser = new TextFieldParser(path, defaultEncoding))
    {
        parser.TextFieldType = FieldType.Delimited;
        //设定逗号分隔符
        parser.SetDelimiters(",");
        //设定不忽略字段前后的空格
        parser.TrimWhiteSpace = false;
        bool isLine = false;
        while (!parser.EndOfData)
        {
            string[] fields = parser.ReadFields();
            if (isLine)
            {
                var obj = func(fields);
                if (obj != null) list.Add(obj);
            }
            else
            {
                //忽略标题行业
                isLine = true;
            }
        }
    }
    return list;
}
#endregion

使用时,代码如下:

//读CSV文件
records = CsvFile.Read(path, Test.Parse);

使用正则表达式解析CSV文件

如果你有一个问题,想用正则表达式来解决,那么你就有两个问题了。

正则表达式有一定的学习门槛,而且学习后不经常使用就会忘记。正则表达式解决的大多数是一些不易变更需求的问题,这就导致一个稳定可用的正则表达式可以传好几代。
本节的正则表达式来自 《精通正则表达式(第3版)》 第6章 打造高效正则表达式——简单的消除循环的例子,有兴趣的可以去了解一下,表达式说明如下:


注:这本书最终版的解析CSV文件的正则表达式是Jave版的使用占有优先量词取代固化分组的版本,也是百度上经常见到的版本。不过占有优先量词在C#中有点问题,本人能力有限解决不了,所以使用了上图的版本。不过,这两版正则表达式性能上没有差异。

正则表达式解析CSV文件代码如下:

#region 读CSV文件(使用正则表达式)
/// <summary>
/// 读CSV文件,默认第一行为标题
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="path">文件路径</param>
/// <param name="func">字段解析规则</param>
/// <param name="defaultEncoding">文件编码</param>
/// <returns></returns>
public static List<T> Read_Regex<T>(string path, Func<string[], T> func, Encoding defaultEncoding = null) where T : class
{
    List<T> list = new List<T>();
    StringBuilder sbr = new StringBuilder(100);
    Regex lineReg = new Regex("\"");
    Regex fieldReg = new Regex("\\G(?:^|,)(?:\"((?>[^\"]*)(?>\"\"[^\"]*)*)\"|([^\",]*))");
    Regex quotesReg = new Regex("\"\"");

    bool isLine = false;
    string line = string.Empty;
    using (StreamReader sr = new StreamReader(path))
    {
        while (null != (line = ReadLine(sr)))
        {
            sbr.Append(line);
            string str = sbr.ToString();
            //一个完整的CSV记录行,它的双引号一定是偶数
            if (lineReg.Matches(sbr.ToString()).Count % 2 == 0)
            {
                if (isLine)
                {
                    var fields = ParseCsvLine(sbr.ToString(), fieldReg, quotesReg).ToArray();
                    var obj = func(fields.ToArray());
                    if (obj != null) list.Add(obj);
                }
                else
                {
                    //忽略标题行业
                    isLine = true;
                }
                sbr.Clear();
            }
            else
            {
                sbr.Append(Environment.NewLine);
            }                   
        }
    }
    if (sbr.Length > 0)
    {
        //有解析失败的字符串,报错或忽略
    }
    return list;
}

//重写ReadLine方法,只有\r\n才是正确的一行
private static string ReadLine(StreamReader sr) 
{
    StringBuilder sbr = new StringBuilder();
    char c;
    int cInt;
    while (-1 != (cInt =sr.Read()))
    {
        c = (char)cInt;
        if (c == '\n' && sbr.Length > 0 && sbr[sbr.Length - 1] == '\r')
        {
            sbr.Remove(sbr.Length - 1, 1);
            return sbr.ToString();
        }
        else 
        {
            sbr.Append(c);
        }
    }
    return sbr.Length>0?sbr.ToString():null;
}

private static List<string> ParseCsvLine(string line, Regex fieldReg, Regex quotesReg)
{
    var fieldMath = fieldReg.Match(line);
    List<string> fields = new List<string>();
    while (fieldMath.Success)
    {
        string field;
        if (fieldMath.Groups[1].Success)
        {
            field = quotesReg.Replace(fieldMath.Groups[1].Value, "\"");
        }
        else
        {
            field = fieldMath.Groups[2].Value;
        }
        fields.Add(field);
        fieldMath = fieldMath.NextMatch();
    }
    return fields;
}
#endregion

使用时代码如下:

//读CSV文件
records = CsvFile.Read_Regex(path, Test.Parse);

目前还未发现正则表达式解析有什么bug,不过还是不建议使用。

完整的CsvFile工具类

完整的CsvFile类代码如下:

using Microsoft.VisualBasic.FileIO;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;


namespace ConsoleApp4
{
    /// <summary>
    /// CSV文件读写工具类
    /// </summary>
    public class CsvFile
    {
        #region 写CSV文件
        //字段数组转为CSV记录行
        private static string FieldsToLine(IEnumerable<string> fields)
        {
            if (fields == null) return string.Empty;
            fields = fields.Select(field =>
            {
                if (field == null) field = string.Empty;
                //所有字段都加双引号
                field = string.Format("\"{0}\"", field.Replace("\"", "\"\""));

                //不简化
                //field = field.Replace("\"", "\"\"");
                //if (field.IndexOfAny(new char[] { ',', '"', ' ', '\r' }) != -1)
                //{
                //    field = string.Format("\"{0}\"", field);
                //}
                return field;
            });
            string line = string.Format("{0}{1}", string.Join(",", fields), Environment.NewLine);
            return line;
        }

        //默认的字段转换方法
        private static IEnumerable<string> GetObjFields<T>(T obj, bool isTitle) where T : class
        {
            IEnumerable<string> fields;
            if (isTitle)
            {
                fields = obj.GetType().GetProperties().Select(pro => pro.Name);
            }
            else
            {
                fields = obj.GetType().GetProperties().Select(pro => pro.GetValue(obj)?.ToString());
            }
            return fields;
        }

        /// <summary>
        /// 写CSV文件,默认第一行为标题
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list">数据列表</param>
        /// <param name="path">文件路径</param>
        /// <param name="append">追加记录</param>
        /// <param name="func">字段转换方法</param>
        /// <param name="defaultEncoding"></param>
        public static void Write<T>(List<T> list, string path,bool append=true, Func<T, bool, IEnumerable<string>> func = null, Encoding defaultEncoding = null) where T : class
        {
            if (list == null || list.Count == 0) return;
            if (defaultEncoding == null)
            {
                defaultEncoding = Encoding.UTF8;
            }
            if (func == null)
            {
                func = GetObjFields;
            }
            if (!File.Exists(path)|| !append)
            {
                var fields = func(list[0], true);
                string title = FieldsToLine(fields);
                File.WriteAllText(path, title, defaultEncoding);
            }
            using (StreamWriter sw = new StreamWriter(path, true, defaultEncoding))
            {
                list.ForEach(obj =>
                {
                    var fields = func(obj, false);
                    string line = FieldsToLine(fields);
                    sw.Write(line);
                });
            }
        }
        #endregion

        #region 读CSV文件(使用TextFieldParser)
        /// <summary>
        /// 读CSV文件,默认第一行为标题
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="path">文件路径</param>
        /// <param name="func">字段解析规则</param>
        /// <param name="defaultEncoding">文件编码</param>
        /// <returns></returns>
        public static List<T> Read<T>(string path, Func<string[], T> func, Encoding defaultEncoding = null) where T : class
        {
            if (defaultEncoding == null)
            {
                defaultEncoding = Encoding.UTF8;
            }
            List<T> list = new List<T>();
            using (TextFieldParser parser = new TextFieldParser(path, defaultEncoding))
            {
                parser.TextFieldType = FieldType.Delimited;
                //设定逗号分隔符
                parser.SetDelimiters(",");
                //设定不忽略字段前后的空格
                parser.TrimWhiteSpace = false;
                bool isLine = false;
                while (!parser.EndOfData)
                {
                    string[] fields = parser.ReadFields();
                    if (isLine)
                    {
                        var obj = func(fields);
                        if (obj != null) list.Add(obj);
                    }
                    else
                    {
                        //忽略标题行业
                        isLine = true;
                    }
                }
            }
            return list;
        }
        #endregion

        #region 读CSV文件(使用正则表达式)
        /// <summary>
        /// 读CSV文件,默认第一行为标题
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="path">文件路径</param>
        /// <param name="func">字段解析规则</param>
        /// <param name="defaultEncoding">文件编码</param>
        /// <returns></returns>
        public static List<T> Read_Regex<T>(string path, Func<string[], T> func, Encoding defaultEncoding = null) where T : class
        {
            List<T> list = new List<T>();
            StringBuilder sbr = new StringBuilder(100);
            Regex lineReg = new Regex("\"");
            Regex fieldReg = new Regex("\\G(?:^|,)(?:\"((?>[^\"]*)(?>\"\"[^\"]*)*)\"|([^\",]*))");
            Regex quotesReg = new Regex("\"\"");

            bool isLine = false;
            string line = string.Empty;
            using (StreamReader sr = new StreamReader(path))
            {
                while (null != (line = ReadLine(sr)))
                {
                    sbr.Append(line);
                    string str = sbr.ToString();
                    //一个完整的CSV记录行,它的双引号一定是偶数
                    if (lineReg.Matches(sbr.ToString()).Count % 2 == 0)
                    {
                        if (isLine)
                        {
                            var fields = ParseCsvLine(sbr.ToString(), fieldReg, quotesReg).ToArray();
                            var obj = func(fields.ToArray());
                            if (obj != null) list.Add(obj);
                        }
                        else
                        {
                            //忽略标题行业
                            isLine = true;
                        }
                        sbr.Clear();
                    }
                    else
                    {
                        sbr.Append(Environment.NewLine);
                    }                   
                }
            }
            if (sbr.Length > 0)
            {
                //有解析失败的字符串,报错或忽略
            }
            return list;
        }

        //重写ReadLine方法,只有\r\n才是正确的一行
        private static string ReadLine(StreamReader sr) 
        {
            StringBuilder sbr = new StringBuilder();
            char c;
            int cInt;
            while (-1 != (cInt =sr.Read()))
            {
                c = (char)cInt;
                if (c == '\n' && sbr.Length > 0 && sbr[sbr.Length - 1] == '\r')
                {
                    sbr.Remove(sbr.Length - 1, 1);
                    return sbr.ToString();
                }
                else 
                {
                    sbr.Append(c);
                }
            }
            return sbr.Length>0?sbr.ToString():null;
        }
       
        private static List<string> ParseCsvLine(string line, Regex fieldReg, Regex quotesReg)
        {
            var fieldMath = fieldReg.Match(line);
            List<string> fields = new List<string>();
            while (fieldMath.Success)
            {
                string field;
                if (fieldMath.Groups[1].Success)
                {
                    field = quotesReg.Replace(fieldMath.Groups[1].Value, "\"");
                }
                else
                {
                    field = fieldMath.Groups[2].Value;
                }
                fields.Add(field);
                fieldMath = fieldMath.NextMatch();
            }
            return fields;
        }
        #endregion

    }
}

使用方法如下:

//写CSV文件
CsvFile.Write(records, path, true, new Func<Test, bool, IEnumerable<string>>((obj, isTitle) =>
{
    IEnumerable<string> fields;
    if (isTitle)
    {
        fields = obj.GetType().GetProperties().Select(pro => pro.Name + Environment.NewLine + "\",\"");
    }
    else
    {
        fields = obj.GetType().GetProperties().Select(pro => pro.GetValue(obj)?.ToString());
    }
    return fields;
}));

//读CSV文件
records = CsvFile.Read(path, Test.Parse);

//读CSV文件
records = CsvFile.Read_Regex(path, Test.Parse);

总结

  • 介绍了CSV文件的 RFC 4180 标准及其简化理解版本
  • 介绍了CsvHelperTextFieldParser正则表达式三种解析CSV文件的方法
  • 项目中推荐使用CsvHelper,如果不想引入太多开源组件可以使用TextFieldParser,不建议使用正则表达式

附录

原文地址:https://www.cnblogs.com/timefiles/p/CsvReadWrite.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


项目中经常遇到CSV文件的读写需求,其中的难点主要是CSV文件的解析。本文会介绍CsvHelper、TextFieldParser、正则表达式三种解析CSV文件的方法,顺带也会介绍一下CSV文件的写方法。 CSV文件标准 在介绍CSV文件的读写方法前,我们需要了解一下CSV文件的格式。 文件示例 一
简介 本文的初衷是希望帮助那些有其它平台视觉算法开发经验的人能快速转入Halcon平台下,通过文中的示例开发者能快速了解一个Halcon项目开发的基本步骤,让开发者能把精力完全集中到算法的开发上面。 首先,你需要安装Halcon,HALCON 18.11.0.1的安装包会放在文章末尾。安装包分开发和
这篇文章主要简单记录一下C#项目的dll文件管理方法,以便后期使用。 设置dll路径 参考C#开发奇技淫巧三:把dll放在不同的目录让你的程序更整洁中间的 方法一:配置App.config文件的privatePath : &lt;runtime&gt; &lt;assemblyBinding xml
在C#中的使用JSON序列化及反序列化时,推荐使用Json.NET——NET的流行高性能JSON框架,当然也可以使用.NET自带的 System.Text.Json(.NET5)、DataContractJsonSerializer、JavaScriptSerializer(不推荐)。
事件总线是对发布-订阅模式的一种实现,是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。&#xA;EventBus维护一个事件的字典,发布者、订阅者在事件总线中获取事件实例并执行发布、订阅操作,事件实例负责维护、执行事件处理程序。
通用翻译API的HTTPS 地址为https://fanyi-api.baidu.com/api/trans/vip/translate,使用方法参考通用翻译API接入文档 。&#xA;请求方式可使用 GET 或 POST 方式(Content-Type 请指定为:application/x-www-for
词云”由美国西北大学新闻学副教授、新媒体专业主任里奇·戈登(Rich Gordon)于2006年最先使用,是通过形成“关键词云层”或“关键词渲染”,对文本中出现频率较高的“关键词”的视觉上的突出。词云图过滤掉大量的文本信息,使浏览者只要一眼扫过文本就可以领略文本的主旨。&#xA;网上大部分文章介绍的是使用P
微软在.NET中对串口通讯进行了封装,我们可以在.net2.0及以上版本开发时直接使用SerialPort类对串口进行读写操作。&#xA;为操作方便,本文对SerialPort类做了一些封装,暂时取名为**SerialPortClient**。
简介 管道为进程间通信提供了平台, 管道分为两种类型:匿名管道、命名管道,具体内容参考.NET 中的管道操作。简单来说,匿名管道只能用于本机的父子进程或线程之间,命名管道可用于远程主机或本地的任意两个进程,本文主要介绍命名管道的用法。 匿名管道在本地计算机上提供进程间通信。 与命名管道相比,虽然匿名
目录自定义日志类NLog版本的日志类Serilog版本的日志类 上个月换工作,新项目又要重新搭建基础框架,把日志实现部分单独记录下来方便以后参考。 自定义日志类 代码大部分使用ChatGPT生成,人工进行了测试和优化,主要特点: 线程安全,日志异步写入文件不影响业务逻辑 支持过期文件自动清理,也可自
[TOC] # 原理简介 本文参考[C#/WPF/WinForm/程序实现软件开机自动启动的两种常用方法](https://blog.csdn.net/weixin_42288432/article/details/120059296),将里面中的第一种方法做了封装成**AutoStart**类,使
简介 FTP是FileTransferProtocol(文件传输协议)的英文简称,而中文简称为“文传协议”。用于Internet上的控制文件的双向传输。同时,它也是一个应用程序(Application)。基于不同的操作系统有不同的FTP应用程序,而所有这些应用程序都遵守同一种协议以传输文件。 FTP
使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。 将特性与程序实体相关联后,可以在运行时使用反射这项技术查询特性。&#xA;在 C# 中,通过用方括号 ([]) 将特性名称括起来,并置于应用该特性的实体的声明上方以指定特性。
# 简介 主流的识别库主要有ZXing.NET和ZBar,OpenCV 4.0后加入了QR码检测和解码功能。本文使用的是ZBar,同等条件下ZBar识别率更高,图片和部分代码参考[在C#中使用ZBar识别条形码](https://www.cnblogs.com/w2206/p/7755656.htm
C#中Description特性主要用于枚举和属性,方法比较简单,记录一下以便后期使用。 扩展类DescriptionExtension代码如下: using System; using System.ComponentModel; using System.Reflection; /// &lt;
本文实现一个简单的配置类,原理比较简单,适用于一些小型项目。主要实现以下功能:保存配置到json文件、从文件或实例加载配置类的属性值、数据绑定到界面控件。&#xA;一般情况下,项目都会提供配置的设置界面,很少手动更改配置文件,所以选择以json文件保存配置数据。
前几天用SerialPort类写一个串口的测试程序,关闭串口的时候会让界面卡死。网上大多数方法都是定义2个bool类型的标记Listening和Closing,关闭串口和接受数据前先判断一下。我的方法是DataReceived事件处理程序用this.BeginInvoke()更新界面,不等待UI线程
约束告知编译器类型参数必须具备的功能。 在没有任何约束的情况下,类型参数可以是任何类型。 编译器只能假定 System.Object 的成员,它是任何 .NET 类型的最终基类。 如果客户端代码使用不满足约束的类型,编译器将发出错误。 通过使用 where 上下文关键字指定约束。&#xA;最常用的泛型约束为
protobuf-net是用于.NET代码的基于契约的序列化程序,它以Google设计的“protocol buffers”序列化格式写入数据,适用于大多数编写标准类型并可以使用属性的.NET语言。&#xA;protobuf-net可通过NuGet安装程序包,也可直接访问github下载源码:https:/
工作中经常遇到需要实现TCP客户端或服务端的时候,如果每次都自己写会很麻烦且无聊,使用SuperSocket库又太大了。这时候就可以使用SimpleTCP了,当然仅限于C#语言。&#xA;SimpleTCP是一个简单且非常有用的 .NET 库,用于处理启动和使用 TCP 套接字(客户端和服务器)的重复性任务