C# in Depth,4th Edition的第九章读后总结

闲话:说实在的,这一章看来看去有点头晕,但是其实主要就是两个问题:string.Format的简化使用方法和FormattableString解决属地化、Sql注入的问题。

string.Format的使用方法和简化方法

        string s1 = "csharp1";
        string s2 = "csharp2";
        string res = string.Format("s1={0},s2={1}", s1,s2);
        Console.WriteLine(res);

通常你想在string字符串中插入一些实参时,用string.Format是这样做的。在C中是这样:

#include <stdio.h>

int main() {
    int x = 5;
    float y = 2.5;
    printf("The values of x and y are: %d, %f\n", x, y);
    return 0;
}

这些{0},{1},%d,%f理解为占位符就行了。
对于string.Format这种使用方式,现在有更加方便的:

    /// <summary>
    /// 简单内插
    /// </summary>
    public void SimpleEmbedded()
    {
        string? name = Console.ReadLine();
        //有趣的是按Ctrl+Z,再回车,name就会是null
        if (name == null)
            Console.WriteLine("parameterName is null");
        Console.WriteLine($"Hello,{name}!");
    }

直接将{0}替换成{name},可读性增强了。这种写法还可以设置对齐:

    /// <summary>
    /// 内插字符串字面量格式化字符串
    /// </summary>
    /// <param parameterName="price"></param>
    public void FormateString(decimal price)
    {
        decimal tip = price * 0.2m;
        //9位整数右对齐
        Console.WriteLine($"Price: {price,9:C}");//C是Currency。还可以填E、F、G、N、P。9是宽度为9,右对齐。-9是宽度为9,左对齐
        //9位整数左对齐
        Console.WriteLine($"Tip: {tip,-9:E}");//E是科学计数法
        Console.WriteLine($"Tip: {tip,-9:F}");//F是float
        Console.WriteLine($"Tip: {tip * 1000,-9:G}");//G是general,通用
        Console.WriteLine($"Tip: {tip * 1000,-9:N}");//N是千位分隔符的数字
        Console.WriteLine($"Tip: {tip,-9:P}");//P是percent
        //9位整数右对齐
        Console.WriteLine($@"\n Total: {tip + price,9:C}");//@可以取消转义

        Console.WriteLine(string.Format("price={0}, tip={1}", price, tip));
    }

FormattableString解决属地化

属地化是什么呢?其实就是不同地区对于同一条代码DateTime.Now的值可能是不同的,或者同一个日期DateTime,格式化的结果也是不同的。

    /// <summary>
    /// 不同的Culture中,Format一个DateTime的结果
    /// 不同的地区可能显示DateTime都是不一样的
    /// </summary>
    public void DateTimeFormatInAllCultures()
    {
        CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
        var birthDate = new DateTime(1976, 6, 19);
        foreach (var culture in cultures)
        {
            string text = string.Format(
                culture, "{0,-15}{1,12:d}", culture.Name, birthDate);
            Console.WriteLine(text);
        }
    }

那么如何用FormattableString进行属地化?看下面的代码片段即可,哈哈!还好我在抄书上的代码时写了很多注释,直接看代码就行了!

/// <summary>
/// 用FormattableString完成属地化,
/// 注意string里有两个ToString,string.ToString()和string.ToString(IFormatProvider? provider),
/// 但看源码其实方法都是return this,也就是返回该字符串本身,所以没有用。
/// </summary>
public void LocalizationString()
{
    var dateOfBirth = new DateTime(1976, 6, 19);
    FormattableString formattableString = $"Jon was born on {dateOfBirth:D}";
    //string formattableString = $"Jon was born on {dateOfBirth:D}";
    var culture = CultureInfo.GetCultureInfo("en-US");
    var result = formattableString.ToString(culture);
    Console.WriteLine(result);

    // invariant方法,该CultureInfo不随地区变化,使用固定的规则和格式。为什么str1已经有yyyy-MM-dd了,还要Invariant方法呢?
    // str2和str1的输出完全是一样的。
    // 书上原话是:
    /// "DateTime的值会使用当前culture默认的日历系统表示,如果要格式化的日期是2016年10月21日;
    /// 目标culture是ar-SA(沙特阿拉伯的阿拉伯语),所得年份会是1438年。"
    // 可以这么理解:程序在中国运行时获得一个日期date,
    // 但是在阿拉伯,这个DateTime的值将会是1438年。如果我直接将str2作为格式化结果,就会出问题。
    // 所以我要用Invariant方法,这样就不会出问题。
    DateTime date = DateTime.UtcNow;
    string str1 = FormattableString.Invariant($"x={date:yyyy-MM-dd}");
    string str2 = $"x={date:yyyy-MM-dd}";
    Console.WriteLine($"str1:{str1}");
    Console.WriteLine($"str2:{str2}");
}

FormattableString解决Sql注入问题

    /// <summary>
    /// 在需要将用户输入和sql语句混用时,如何避免SQL注入呢?
    /// 
    /// </summary>
    public void SQL_Injection()
    {
        //反面案例如下,这里用户输入的tag是不可控的,要是输入:
        // tag='; DROP TABLE Entries;--
        // 的话,整个sql就是:SELECT Description FROM Entries WHERE Tag = ''; DROP TABLE Entries;--' AND UserId = {userId},那人家就可以通过输入tag把你的表给删了

        /*var tag = Console.ReadLine();
        using (var conn = new SqlConnection(connectionString))
        {
            conn.Open();
            string sql =
                $@"SELECT  Description FROM Entries
                       WHERE Tag ='{tag}' AND UserId={userId}";
            using (var cmd = new SqlCommand(sql, conn))
            {
                using (var reader = command.ExecuteReader())
                {
                    ...
                }
            }
        }*/
        //当然是有补救的方法,可以用command.Parameters.Add(...)规避风险
        //但是这样的话代码就不美观了。用FormattableString可以既安全又美观
        /*
        var tag = Console.ReadLine();
        using (var conn =new SqlConnection(connectionString))
        {
            conn.Open();
            //这里NewSqlCommand是自定义扩展方法
            using(var command = conn.NewSqlCommand(
                    $@"SELECT Description FROM Entries
                        WHERE Tag = {tag:NVarChar} 
                        AND UserId = {userId:Int}"))
            {
                using(var reader = command.ExecuteReader())
                {
                }
            }
        }
         */
    }
}

static class ExtensionClass
{
    static void NewSqlCommand(this string conn, FormattableString formattableString)
    {
        //用FormattableString的GetArguments方法将实参填入字符串,还能根据{tag:NVarChar}限定类型
        SqlParameter[] sqlParameters = formattableString.GetArguments()
            .Select((value,position)=>
                new SqlParameter(Invariant($"@p{position}"),value))
                .ToArray();
        object[] formatArguments = sqlParameters
            .Select(p => new FormatCapturingParameter(p))
            .ToArray();
    }
}
class FormatCapturingParameter : IFormattable
{
    private readonly SqlParameter param;
    internal FormatCapturingParameter(SqlParameter param)
    {
        this.param = param;
    }
    public string ToString(string? format,IFormatProvider? formatProvider)
    {
        if(!string.IsNullOrEmpty(format))
        {
             param.SqlDbType = (SqlDbType)Enum.Parse(
                 typeof(SqlDbType), format, ignoreCase: true);
        }    
            return param.ParameterName;
    }
}
class SqlParameter
{
    public SqlParameter(string parameterName, object value)
    {
        ParameterName = parameterName;
        Value = value;
    }
    private SqlDbType _sqlDbType;
    public SqlDbType SqlDbType { get; set; }
    public string ParameterName { get; }
    public object Value { get; }
}

总结

看书的时候将示例代码抄下来,在代码中写好注释,这比在blog中啰里吧嗦写一大堆中文要直接的多。

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值