zl程序教程

您现在的位置是:首页 >  后端

当前栏目

.net 反射访问私有变量和私有方法 如何创建C# Closure ? C# 批量生成随机密码,必须包含数字和字母,并用加密算法加密 C#中的foreach和yield 数组为什么可以使用linq查询 C#中的 具名参数 和 可选参数 显示实现接口 异步CTP(Async CTP)为什么那样工作? C#多线程基础,适合新手了解 C#加快Bitmap的访问速度 C#实现对图片文件的压

2023-09-11 14:14:39 时间

以下为本次实践代码:

复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            //反射读取类私有属性
            Person per = new Person("ismallboy", "20102100104");
            Type t = per.GetType();
            //获取私有方法
            MethodInfo method = t.GetMethod("GetStuInfo", BindingFlags.NonPublic | BindingFlags.Instance);
            //访问无参数私有方法
            string strTest = method.Invoke(per, null).ToString();
            //访问有参数私有方法
            MethodInfo method2 = t.GetMethod("GetValue", BindingFlags.NonPublic | BindingFlags.Instance);
            object[] par = new object[2];
            par[0] = "ismallboy";
            par[1] = 2;
            string strTest2 = method2.Invoke(per, par).ToString();

            //获取私有字段
            PropertyInfo field = t.GetProperty("Name", BindingFlags.NonPublic | BindingFlags.Instance);
            //访问私有字段值
            string value = field.GetValue(per).ToString();
            //设置私有字段值
            field.SetValue(per, "new Name");
            value = field.GetValue(per).ToString();
        }
    }

    /// <summary>
    /// 个人信息
    /// </summary>
    class Person
    {
        private string Name { get; set; }
        private string StuNo { get; set; }

        public Person(string name, string stuNo)
        {
            this.Name = name;
            this.StuNo = stuNo;
        }

        private string GetStuInfo()
        {
            return this.Name;
        }

        private string GetValue(string str, int n)
        {
            return str + n.ToString();
        }
    }
}
复制代码

如果使用dynamic的话,也可以如下:

复制代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleTest
{
    class Program
    {
        static void Main(string[] args)
        {
            //反射读取类私有属性
            dynamic per = new Person("ismallboy", "20102100104");
            Type t = per.GetType();
            //获取私有方法
            MethodInfo method = t.GetMethod("GetStuInfo", BindingFlags.NonPublic | BindingFlags.Instance);
            //访问无参数私有方法
            string strTest = method.Invoke(per, null);
            //访问有参数私有方法
            MethodInfo method2 = t.GetMethod("GetValue", BindingFlags.NonPublic | BindingFlags.Instance);
            object[] par = new object[2];
            par[0] = "ismallboy";
            par[1] = 2;
            string strTest2 = method2.Invoke(per, par);

            //获取私有字段
            PropertyInfo field = t.GetProperty("Name", BindingFlags.NonPublic | BindingFlags.Instance);
            //访问私有字段值
            string value = field.GetValue(per);
            //设置私有字段值
            field.SetValue(per, "new Name");
            value = field.GetValue(per);
        }
    }

    /// <summary>
    /// 个人信息
    /// </summary>
    class Person
    {
        private string Name { get; set; }
        private string StuNo { get; set; }

        public Person(string name, string stuNo)
        {
            this.Name = name;
            this.StuNo = stuNo;
        }

        private string GetStuInfo()
        {
            return this.Name;
        }

        private string GetValue(string str, int n)
        {
            return str + n.ToString();
        }
    }
}
复制代码

 

 

 

JavaScript中一个重要的概念就是闭包,闭包在JavaScript中有大量的应用,但是你知道么?C#也可以创建Closure。下面就介绍一下如何在C#中创建神奇的闭包。

  在这之前,我们必须先知道如何在C#中定义函数

  //函数定义,参数为string,返回为string
 Func<string, string> myFunc = delegate(string msg)
 {
       return "Msg:" + msg;
 };

利用Lambda表达式也可以简化上述的代码,但是效果一样:

  //Lambda
 Func<string, string> myFuncSame = msg => "Msg:" + msg;

定义好函数后,可以进行调用:

  //函数调用
  string message= myFuncSame("Hello world");

定义一个带外部变量(相对于内嵌函数而言)的嵌套函数,外部函数将内部嵌套的函数进行返回:

复制代码
public static Func<int, int> Func()
 {
     var myVar = 1;
     Func<int, int> inc = delegate(int var1)
     {
         //myVar能够记录上一次调用后的状态(值)
         myVar = myVar + 1;
         return var1 + myVar;
     };
     return inc;
 }
复制代码

C# Closure调用如下所示:

复制代码
static void CsharpClosures()
 {
     var inc = Func();
     Console.WriteLine(inc(5));//7
     Console.WriteLine(inc(6));//9
 }
复制代码

当第二次调用inc(6)时,此时函数内变量myVar并未像第一次调用函数时进行重新初始化(var myVar=1),而是保留了第一次运算的值,即 2,因此inc(6)返回的结果为(2+1+6)=9.

 

要求:密码必须包含数字和字母

思路:1.列出数字和字符。 组成字符串 :chars

        2.利用randrom.Next(int i)返回一个小于所指定最大值的非负随机数。

        3. 随机取不小于chars长度的随机数a,取字符串chars的第a位字符。

        4.循环 8次,得到8位密码

        5.循环N次,批量得到密码。

代码实现如下 Main函数:

复制代码
static void Main(string[] args)
        {
            string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
            Random randrom = new Random((int)DateTime.Now.Ticks);
            string path1 = @"C:\Users\lenovo\Desktop\pws.txt";
            for (int j = 0; j < 10000;j++ )
            {
                string str = "";
                for (int i = 0; i < 8; i++)
                {
                    str += chars[randrom.Next(chars.Length)];//randrom.Next(int i)返回一个小于所指定最大值的非负随机数
                }
                if (IsNumber(str))//判断是否全是数字
                    continue;
                if (IsLetter(str))//判断是否全是字母
                    continue;
                File.AppendAllText(path1, str);
                string pws = Md5(str,32);//MD5加密
                File.AppendAllText(path1, "," + pws + "\r\n");
            }
            Console.WriteLine("ok");
            Console.Read();
        }
复制代码

巧用String.trim 函数,判断是否全是数字,全是字母。

说明:string.trim   从 String 对象移除前导空白字符和尾随空白字符。

返回:一个字符串副本,其中从该字符串的开头和末尾移除了所有空白字符。

有一个重载:string.Trim(params char[] trimChars)   

//从当前System.string对象移除数组中指定的一组字符的所有前导匹配项和尾部匹配项

 trimChars:要删除的字符数组

方法实现如下代码:

复制代码
        //判断是否全是数字
        static bool IsNumber(string str)
        {
            if (str.Trim("0123456789".ToCharArray()) == "")
                return true;
            return false;
        }
        //判断是否全是字母
        static bool IsLetter(string str)
        {
           if (str.Trim("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".ToCharArray()) == "")
               return true;
           return false;
        }
复制代码

 用MD5加密,算法代码实现如下:

复制代码
/// <summary>
        /// MD5加密
        /// </summary>
        /// <param name="str">加密字元</param>
        /// <param name="code">加密位数16/32</param>
        /// <returns></returns>
        public static string Md5(string str, int code)
        {
            string strEncrypt = string.Empty;

            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] fromData = Encoding.GetEncoding("GB2312").GetBytes(str);
            byte[] targetData = md5.ComputeHash(fromData);
            for (int i = 0; i < targetData.Length; i++)
            {
                strEncrypt += targetData[i].ToString("X2");
            }
            if (code == 16)
            {
                strEncrypt = strEncrypt.Substring(8, 16);
            }
            return strEncrypt;
        }
复制代码

生成批量密码,和加密后的密码如下图:

 

1. foreach

C#编译器会把foreach语句转换为IEnumerable接口的方法和属性。

foreach (Person p in persons)
 {
     Console.WriteLine(p);
 }

foreach语句会解析为下面的代码段。

调用GetEnumerator()方法,获得数组的一个枚举

在while循环中,只要MoveNext()返回true,就一直循环下去

用Current属性访问数组中的元素

复制代码
IEnumerator enumerator = persons. GetEnumerator();
 while (enumerator.MoveNext())
 {
    Person p = (Person) enumerator.Current;
    Console.WriteLine(p);
}
复制代码

2. yield语句

yield语句的两种形式:

yield return <expression>;
yield break;

使用一个yield return语句返回集合的一个元素

包含yield语句的方法或属性是迭代器。迭代器必须满足以下要求

a. 返回类型必须是IEnumerableIEnumerable<T>IEnumerator或 IEnumerator<T>

b. 它不能有任何ref或out参数

yield return语句不能位于try-catch快。yield return语句可以位于try-finally的try块

复制代码
try
              {
                  // ERROR: Cannot yield a value in the boday of a try block with a catch clause
                 yield return "test";
              }
             catch
             { }
 
              try
             {
                 // 
                 yield return "test again";
             }
             finally
             { }
 
             try
             { }
             finally
             { 
                 // ERROR: Cannot yield in the body of a finally clause
                yield return ""; 
             }
复制代码

yield break语句可以位于try块或catch块,但是不能位于finally块

下面的例子是用yield return语句实现一个简单集合的代码,以及用foreach语句迭代集合

复制代码
using System;
using System.Collections.Generic;

namespace ConsoleApplication6
{
    class Program
    {
        static void Main(string[] args)
        {
            HelloCollection helloCollection = new HelloCollection();
            foreach (string s in helloCollection)
            {
                Console.WriteLine(s);
                Console.ReadLine();
            }
        }
    }

    public class HelloCollection
    {
        
        public IEnumerator<String> GetEnumerator()
        {
            // yield return语句返回集合的一个元素,并移动到下一个元素上;yield break可以停止迭代
            yield return "Hello";
            yield return "World";
        }
    }
}
复制代码

使用yield return语句实现以不同方式迭代集合的类:

复制代码
using System;
using System.Collections.Generic;

namespace ConsoleApplication8
{
    class Program
    {
        static void Main(string[] args)
        {
            MusicTitles titles = new MusicTitles();
            foreach (string title in titles)
            {
                Console.WriteLine(title);
            }
            Console.WriteLine();

            foreach (string title in titles.Reverse())
            {
                Console.WriteLine(title);
            }
            Console.WriteLine();

            foreach (string title in titles.Subset(2, 2))
            {
                Console.WriteLine(title);
                Console.ReadLine();
            }
        }
    }

    public class MusicTitles
    {
        string[] names = { "a", "b", "c", "d" };
        public IEnumerator<string> GetEnumerator()
        {
            for (int i = 0; i < 4; i++)
            {
                yield return names[i];
            }
        }

        public IEnumerable<string> Reverse()
        {
            for (int i = 3; i >= 0; i--)
            {
                yield return names[i];
            }
        }

        public IEnumerable<string> Subset(int index, int length)
        {
            for (int i = index; i < index + length; i++)
            {
                yield return names[i];
            }
        }
    }
}
复制代码

 

 

问题引出

  这视乎是个完全不必要进行讨论的话题,因为linq(这里具体是linq to objects)本来就是针对集合类型的,数组类型作为集合类型的一种当然可以使用了。不过我还是想写一下,这个问题源于qq群里一位朋友的提问:.net的数组类型都隐式继承了Array类,该类是一个抽象类,并且实现了IEnumerable、ICollection、IList接口。但linq的方法都是针对实现了IEnumerable<T>泛型接口的,Array类并没有实现这些泛型接口,为什么可以使用这些方法呢?

  linq to objects的本质是通过扩展方法来实现集合的查询,这些扩展方法定义在一个Enumerable的静态类中。Enumerable类下的所有扩展方法的第一个参数都是IEnumerable<T> 类型,表示它可以通过IEnumerable<T>类型进行调用。

浅析数组类型

1. 所有数组类型都隐式派生自Array

  当我们定义一个FileStream[] 数组时,CLR会为当前的AppDomain创建一个FileStream[] 类型,该类型派生自 Array。所以数组是引用类型,在堆中分配内存空间。Array类是一个抽象类,定义了许多关于常用的实例方法和静态方法,供所有的数组类型使用。例如常见的:Length属性,CopyTo方法等等。

2. 所有的数组类型都隐式实现了IEnumerable<T>接口

  就如上面所所的,这是一个理所当然的问题,为了提高开发效率,数组类型理应可以使用linq进行查询。但由于数组可以是多维数组或者非0基数组,所以Array类并没有实现IEnumerable<T>、ICollection<T>、IList<T> 这几个泛型接口,而只是实现了非泛型版本的。实际上,CLR会自动为一维的数组类型实现这些泛型接口(指定T类型参数的具体类型),并且还会为它们的父类实现。例如我们定义一个FileStream[] 数组类型,那么CLR会为我们创建如下的层次类型结构:

                                

由于CLR的隐式实现,才使我们可以将一维数组类型应用在需要IEnumerable<T>泛型接口的地方。

  按照上面的说法,我们可以将FileStream[] 类型的对象传递给如下的方法:

  void F1(IEnumerable<object> oEnumerable);

  void F2(ICollection<Stream> sCollection);

  void F3(IList<FileStream> fList);

  这是对于引用类型而言的,如果是值类型,则不为会它的基类实现这些接口。例如DateTimel类型(基类包括ValueType和Object),DateTime[]数组类型不能传递给上面的F1方法,这是因为值类型的数组的内存布局与引用类型的数组不同。

 

具名参数 和 可选参数 是 C# framework 4.0 出来的新特性。

一. 常规方法定义及调用

复制代码
public void Demo1(string x, int y)
{ 
      //do something...
}


public void Main()
{
      //调用
       Demo1("similar", 22);
}
复制代码

调用时,参数顺序(类型)必须与声明一致,且不可省略。

 

 

二. 可选参数的声明及调用

可选参数分为两种情况: 1. 部分参数可选;   2. 全部参数都是可选

复制代码
//部分可选(x为必选,y为可选)
public void Demo2(string x, int y = 5)
{
      //do something...
}


public void Main()
{
       //调用
       Demo2("similar");       // y不传入实参时,y使用默认值5
       Demo2("similar", 10);   // y传入实参,则使用实参10
}
复制代码

注: 当参数为部分可选时, 可选参数  的声明必须定义在 不可选参数 的后面(如上: y 的声明在 x 之后),不然会出现如下错误提示:

复制代码
//全部可选(x,y 均为可选参数)
public void Demo3(string x = "demo", int y = 5)
{
       //do something...
}

public void Main()
{
       //调用
       Demo3();               // x,y不传入实参时,x,y使用默认值 "demo",5
       Demo3("similar");      // y不传入实参时,y使用默认值5
       Demo3("similar", 10);  // x,y都传入实参
}
复制代码

分先后。

       b. 参数声明定义可以无顺序,但调用时必须与声明时的一致。

上面的调用只写的3种,其实还有一种,就是 x 使用默认值,y 传入实参,即 :  Demo3(10);

但这样调用会报错,因为Demo3的第一个参数是 string 类型,错误消息如图:

但是现在我只想传入y, 不想传入 x ,该怎么办呢,那么就要用到 C#的 具名参数。

 

 

三. 具名参数

具名参数的使用主要是体现在函数调用的时候。

复制代码
public void Main()
{
       //调用
       Demo3();                // x,y不传入实参时,x,y使用默认值 "demo",5
       Demo3("similar");       // y不传入实参时,y使用默认值5
       Demo3("similar", 10);   // x,y都传入实参


       // 具名参数的使用
       Demo3(y:10);
}
复制代码

通过具名参数,我们可以指定特定参数的值,这里我们通过 Demo3(y:10)就可以解决我们上面遇到的问题(x使用默认值,y使用实参)。

 

注: 当使用 具名参数时,调用方法可以不用管参数的声明顺序,即如下调用方式也是可以的:

在调用含有可选参数的方法时,vs中会有智能提示,提示哪些是可以选参数及其默认值,中括号表示可选[]:

 

具名参数 和 可选参数 是 C# framework 4.0 出来的新特性。

一. 常规方法定义及调用

复制代码
public void Demo1(string x, int y)
{ 
      //do something...
}


public void Main()
{
      //调用
       Demo1("similar", 22);
}
复制代码

调用时,参数顺序(类型)必须与声明一致,且不可省略。

 

 

二. 可选参数的声明及调用

可选参数分为两种情况: 1. 部分参数可选;   2. 全部参数都是可选

复制代码
//部分可选(x为必选,y为可选)
public void Demo2(string x, int y = 5)
{
      //do something...
}


public void Main()
{
       //调用
       Demo2("similar");       // y不传入实参时,y使用默认值5
       Demo2("similar", 10);   // y传入实参,则使用实参10
}
复制代码

注: 当参数为部分可选时, 可选参数  的声明必须定义在 不可选参数 的后面(如上: y 的声明在 x 之后),不然会出现如下错误提示:

复制代码
//全部可选(x,y 均为可选参数)
public void Demo3(string x = "demo", int y = 5)
{
       //do something...
}

public void Main()
{
       //调用
       Demo3();               // x,y不传入实参时,x,y使用默认值 "demo",5
       Demo3("similar");      // y不传入实参时,y使用默认值5
       Demo3("similar", 10);  // x,y都传入实参
}
复制代码

分先后。

       b. 参数声明定义可以无顺序,但调用时必须与声明时的一致。

上面的调用只写的3种,其实还有一种,就是 x 使用默认值,y 传入实参,即 :  Demo3(10);

但这样调用会报错,因为Demo3的第一个参数是 string 类型,错误消息如图:

但是现在我只想传入y, 不想传入 x ,该怎么办呢,那么就要用到 C#的 具名参数。

 

 

三. 具名参数

具名参数的使用主要是体现在函数调用的时候。

复制代码
public void Main()
{
       //调用
       Demo3();                // x,y不传入实参时,x,y使用默认值 "demo",5
       Demo3("similar");       // y不传入实参时,y使用默认值5
       Demo3("similar", 10);   // x,y都传入实参


       // 具名参数的使用
       Demo3(y:10);
}
复制代码

通过具名参数,我们可以指定特定参数的值,这里我们通过 Demo3(y:10)就可以解决我们上面遇到的问题(x使用默认值,y使用实参)。

 

注: 当使用 具名参数时,调用方法可以不用管参数的声明顺序,即如下调用方式也是可以的:

在调用含有可选参数的方法时,vs中会有智能提示,提示哪些是可以选参数及其默认值,中括号表示可选[]:

 

接口定义了一系列的行为规范,为类型定义一种Can-Do的功能。例如,实现IEnumerable接口定义了GetEnumerator方法,用于获取一个枚举数,该枚举数支持在集合上进行迭代,也就是我们常说的foreach。接口只是定义行为,具体的实现需要由具体类型负责,实现接口的方法又分为隐式实现与显示实现

一、隐式/显示实现接口方法

  简单的说,我们平时“默认”使用的都是隐式的实现方式。例如:

复制代码
interface ILog
{
    void Log();
}
 
public class FileLogger : ILog
{
    public void Log()
    {
        Console.WriteLine("记录到文件!");
    }
}
复制代码

隐式实现很简单,通常我们约定接口命名以 I 开头,方便阅读。接口内的方法不需要用public,编译器会自动加上。类型中实现接口的方法只能是public,也可以定义成虚方法,由子类重写。现在看显示实现的方式:

复制代码
public class EventLogger : ILog
{
    void ILog.Log()
    {
        Console.WriteLine("记录到系统事件!");
    }
}
复制代码

与上面不同的是,方法用了ILog指明,而且没有(也不能有)public或者private修饰符。

  除了语法上的不同,调用方式也不同,显示实现只能用接口类型的变量来调用,如:

复制代码
FileLogger fileLogger = new FileLogger();
fileLogger.Log(); //正确
EventLogger eventLogger = new EventLogger();           
eventLogger.Log(); //报错
ILog log = new EventLogger();
log.Log(); //正确
复制代码

二、何时使用

  1. c#允许实现多个接口,如果多个接口定义了相同的方法,可以用显示实现的方式加以区分,例如:

复制代码
 
public class EmailLogger : ILog, ISendable
{
    void ILog.Log()
    {
        Console.WriteLine("ILog");
    }
 
    void ISendable.Log()
    {
        Console.WriteLine("ISendable");
    }
}
复制代码

 2. 增强编译时的类型安全和避免值类型装箱

  有了泛型,我们自然可以做到编译时的类型安全和避免值类型装箱的操作。但有时候可能没有对应的泛型版本。例如:IComparable(这里只是举例,实际有IComparable<T>)。如:

复制代码
interface IComparable
{
    int CompareTo(object obj);
}
 
struct ValueType : IComparable
{
    private int x;
    public ValueType(int x)
    {
        this.x = x;
    }
 
    public int CompareTo(object obj)
    {
        return this.x - ((ValueType)obj).x;
    }
} 

   //调用:
    
ValueType vt1 = new ValueType(1);
ValueType vt2 = new ValueType(2);
Console.WriteLine(vt1.CompareTo(vt2)); 
复制代码

由于形参是object,上面的CompareTo会发生装箱;而且无法获得编译时的类型安全,例如我们可以随便传一个string,编译不会报错,等到运行时才抛出InvalidCastException。使用显示实现接口的方式,如:

复制代码
public int CompareTo(ValueType vt)
{
    return this.x - vt.x;
}
 
int IComparable.CompareTo(object obj)
{
    return CompareTo((ValueType)obj);
}  
复制代码

再次执行上面的代码,就不会发生装箱操作,而且可以获得编译时的类型安全了。但是如果我们用接口变量调用,就会再次发生装箱并丧失编译时的类型安全检测能力

IComparable vt1 = new ValueType(1); //装箱
ValueType vt2 = new ValueType(2);
Console.WriteLine(vt1.CompareTo(vt2)); //再次装箱

三、缺点

  1. 显示实现只能用接口类型变量调用,会给人的感觉是某类型实现了该接口却无法调用接口中的方法。特别是写成类库给别人调用时,显示实现的接口方法在vs中按f12都不会显示出来。(这点有人在csdn提问过,为什么某个类型可以不用实现接口方法)

  2. 对于值类型,要调用显示实现的方法,会发生装箱操作。

  3. 无法被子类继承使用。

 

对异步CTP感兴趣有很多原因。异步CTP使异步编程比以前更加容易了。它虽然没有Rx强大,但是更容易学。异步CTP介绍了两个新的关键字,async和await。异步方法(或Lambda表达式)必须返回void,Task或Task<TResult>。这篇文章不是介绍异步CTP的,因为网上有很多这样的文章。这篇文章的目的是把程序员开始使用Async CTP遇到的一些常见问题集中起来。

推断返回类型

当从异步方法返回一个值的时候,此方法体直接返回这个值,但该方法本身被声明为返回一个Task<TResult>。当声明一个返回甲类型的方法却必须返回一个乙类型时,就有点“断连”了。

复制代码
// 实际语法
public async Task<int> GetValue()
{
  await TaskEx.Delay(100);
  return 13; //返回类型是 "int", 而不是"Task<int>"
}
复制代码

问题来了:为什么我不能这么写?

复制代码
// 假想语法
public async int GetValue()
{
  await TaskEx.Delay(100);
  return 13; // 返回类型是 "int"
}
复制代码
思考:该方法如何如何照顾调用者呢?异步方法必须返回一个实际结果类型Task<TResult>的值。因此,GetValue方法会出现返回Task<TResult>的智能提示(在对象浏览器,Reflector等中也是这样的)。
 
在设计之初,推断返回类型已经被考虑到了,但该设计团队已经推断出在异步方法中保持这种“断连”比在代码基上扩大这种“断连”更好。如今这种“断连”仍存在,但比以前更小了。该设计团队的共识是一致的方法签名更佳。
思考:async void 和async Task有什么区别?
一个async Task方法就像任何其他的异步操作一样,只是没有返回值。一个async void方法扮演一种高级操作。async Task方法可能被组合进其他使用using await的异步方法。async void方法可能被用作一个事件句柄。async void方法也有其他重要的属性:在ASP.NET上下文中,它通知web服务器直到它返回,页面才完成。
 
推断返回类型会移除async void 和async Task间的区别:要么所有的异步方法是async void(阻止可组合性),要么都是async Task(阻止它们来自事件句柄,同时对ASP.NET要有一个可选择的方案)。

 异步返回

 
在方法声明返回类型和方法体返回的类型之间仍有“断连”。该设计团队的另一个建议是:给return添加一个关键字来指示return返回的值,但这个也确实没有返回什么,如下所示:
复制代码
// 假想语法
public async Task<int> GetValue()
{
  await TaskEx.Delay(100);
  async return 13; // "async return" 意味着值被包装在Task中
}
复制代码
思考:将大量的代码从同步转为异步。

async return关键字也被考虑到了,但并没有足够的说服力。当把一些同步代码转成异步代码时,这尤其正确。强制人们给每个return语句添加asynchronous就好像是“不必要的忙碌”。比较而言,习惯于“断连”更容易。

推断“async”

async关键字必须用在使用了await关键字的方法上。然而,如果把async用在了一个没有使用await的方法上,也会收到一个警告。

问题:为什么async不能根据await的存在推断出来?

复制代码
//假想语法
public Task<int> GetValue()
{
  // "await" 的存在暗示这是一个 "async" 方法.
  await TaskEx.Delay(100);
  return 13;
}
复制代码

思考:向后兼容性和代码可读性

单字的await关键字具有太大的打破变化。在异步方法上的多字await(如await for)或一个关键字之间的选择,只是在那个方法内部启用await关键字。很明显,使用async标记方法让人类和计算机分析起来更容易,因此设计团队决定使用async/await对。

推断“await”

问题:既然显示包括async有意义(看上面),为什么await不能根据async的存在推断出来呢?

复制代码
// 假想语法
public async Task<int> GetValue()
{
  // 暗示有"await",因为这是一个 "async" 方法.
  TaskEx.Delay(100);
  return 13;
}
复制代码

思考:异步操作的并行组合。

乍一看,推断await推断似乎简化了基本的异步操作。只要所有的等待可以按序列(如一个操作等待,然后另一个,再然后另一个)完成,这个就能很好的工作。然而,当有人考虑并行组合的时候,它崩溃了。

异步CTP中的并行组合使用TaskEx.WhenAny 和TaskEx.WhenAll方法。这有一个简单的例子,这个方法立即开始了两个操作,并且等待它们完成。

复制代码
// 实际语法
public async Task<int> GetValue()
{
  // 异步检索两个部分的值
  // 注意此时它们是没有等待的“not await”
  Task<int> part1 = GetValuePart1();
  Task<int> part2 = GetValuePart2();

  // 等待它们的值到达。
  await TaskEx.WhenAll(part1, part2);

  // 计算我们的结果
  int value1 = await part1; // 实际上没有等待
  int value2 = await part2; //实际上没有等待
  return value1 + value2;
}
复制代码

为了处理并行组合,我们必须有能力说我们将不会await一个表达式。

 

 

一、创建线程

  在整个系列文章中,我们主要使用Visual Studio 2015作为线程编程的主要工具。在C#语言中创建、使用线程只需要按以下步骤编写即可:

1、启动Visual Studio 2016,新建一个控制台应用程序。

2、确保该控制台程序使用.NET Framework 4.6或以上版本。然而在该篇中的所有示例使用较低版本可以正常工作。

3、双击打开该控制台应用程序中的“Program.cs”文件,在其中编写如下代码:

复制代码
using System;
  using System.Threading;
  using static System.Console;
  
  namespace Recipe01
  {
      class Program
      {
          static void PrintNumbers()
         {
             WriteLine("Starting...");
             for (int i = 1; i < 10; i++)
             {
                 WriteLine(i);
             }
         }
 
         static void Main(string[] args)
         {
            Thread t = new Thread(PrintNumbers);
             t.Start();
             PrintNumbers();
         }
     }
 }
复制代码

  在第2行代码处,我们导入了System.Threading命名空间,该命名空间包含了我们编写多线程程序所需要的所有类型。

  在第3行代码处,我们使用了C# 6.0的using static特性,使用了该特性之后,在代码中允许我们在使用System.Console类型的静态方法的时候不需要指定其类型名。

  在第9~16行代码处,我们定义了一个名为“PrintNumbers”的方法,该方法将在“Main”方法和线程中进行调用。

  在第20行代码处,我们创建了一个线程来运行“PrintNumbers”方法,当我们初始化一个线程时,一个“ThreadStart”或“ParameterizedThreadStart”委托的实例被传递给线程的构造方法。

  在第21行代码处,我们启动线程。

  在第22行代码处,我们在“Main”方法中调用“PrintNumbers”方法。

4、运行该控制台应用程序,运行效果(每次运行效果可能不同)如下图所示:

二、中止线程

   在这一节,我们将让线程等待一些时间,在等待的这段时间内,该线程不会消耗操作系统的资源。编写步骤如下:

1、使用Visual Studio 2015创建一个新的控制台应用程序。

2、双击打开“Program.cs”文件,编写代码如下所示:

复制代码
using System;
  using System.Threading;
  using static System.Console;
  
  namespace Recipe01
  {
      class Program
      {
          static void PrintNumbers()
        {
             WriteLine("Starting...");
             for (int i = 1; i < 10; i++)
             {
                 WriteLine(i);
             }
         }
 
         static void Main(string[] args)
         {
             Thread t = new Thread(PrintNumbers);
             t.Start();
             PrintNumbers();
         }
     }
 }
复制代码

3、运行该控制台应用程序,运行效果(每次运行效果可能不同)如下图所示:

三、线程等待

   在这一节中,我们将讲述如何在一个线程执行完毕后,再执行剩余的代码,要完成这个工作,我们不能使用Thread.Sleep方法,因为我们不知道另一个线程精确的执行时间。要使一个线程等待另一个线程执行完毕后再进行其他工作,只需要按下列步骤编写代码即可:

1、使用Visual Studio 2015创建一个新的控制台应用程序。

2、双击打开“Program.cs”文件,编写如下代码:

复制代码
using System;
  using System.Threading;
  using static System.Console;
  using static System.Threading.Thread;
  
  namespace Recipe03
  {
      class Program
      {
        static void PrintNumbersWithDelay()
         {
             WriteLine("Starting...");
 
           for(int i = 1; i < 10; i++)
             {
                 Sleep(TimeSpan.FromSeconds(2));
                 WriteLine(i);
             }
         }

         static void Main(string[] args)
        {
             WriteLine("Starting...");
           Thread t = new Thread(PrintNumbersWithDelay);
          t.Start();
             t.Join();
             WriteLine("Thread completed");
         }
    }
}
复制代码

3、运行该控制台应用程序,运行效果如下图所示:

 在第26行代码处,我们在“Main”方法中调用调用“t.Join”方法,该方法允许我们等待线程t执行完毕后,再执行“Main”方法中剩余的代码。有了该技术,我们可以同步两个线程的执行步骤。第一个线程等待第二个线程执行完毕后,再进行其他的工作,在第一个线程等待期间,第一个线程的状态为“bolcked”状态,和我们调用Thread.Sleep的状态一样。

四、终止线程

  在这一节中,我们将讲述如何终止另一个线程的执行。步骤如下:

1、使用Visual Studio 2015创建一个新的控制台应用程序。

2、双击打开“Program.cs”文件,编写如下代码:

复制代码
 using System;
 using System.Threading;
  using static System.Console;
  using static System.Threading.Thread;
  
  namespace Recipe04
  {
      class Program
      {
         static void PrintNumbers()
         {
             WriteLine("Starting...");
 
             for (int i = 1; i < 10; i++)
             {
                 WriteLine(i);
             }
         }
 
         static void PrintNumbersWithDelay()
         {
             WriteLine("Starting...");
             for (int i = 1; i < 10; i++)
             {
                 Sleep(TimeSpan.FromSeconds(2));
                 WriteLine(i);
             }
         }
 
         static void Main(string[] args)
         {
             WriteLine("Starting program...");
             Thread t = new Thread(PrintNumbersWithDelay);
             t.Start();
             Thread.Sleep(TimeSpan.FromSeconds(6));
             t.Abort();
             WriteLine("A thread has been aborted");
             t = new Thread(PrintNumbers);
             t.Start();
             PrintNumbers();
         }
     }
 }
复制代码

3、运行该控制台应用程序,运行效果(每次运行效果可能不同)如下图所示:

 

 

在对Bitmap图片操作的时候,有时需要用到获取或设置像素颜色方法:GetPixel 和 SetPixel,

如果直接对这两个方法进行操作的话速度很慢,这里我们可以通过把数据提取出来操作,然后操作完在复制回去可以加快访问速度

其实对Bitmap的访问还有两种方式,一种是内存法,一种是指针法

1、内存法

  这里定义一个类LockBitmap,通过把Bitmap数据拷贝出来,在内存上直接操作,操作完成后在拷贝到Bitmap中

复制代码
public class LockBitmap
        {
            Bitmap source = null;
            IntPtr Iptr = IntPtr.Zero;
            BitmapData bitmapData = null;

            public byte[] Pixels { get; set; }
            public int Depth { get; private set; }
            public int Width { get; private set; }
            public int Height { get; private set; }

            public LockBitmap(Bitmap source)
            {
                this.source = source;
            }

            /// <summary>
            /// Lock bitmap data
            /// </summary>
            public void LockBits()
            {
                try
                {
                    // Get width and height of bitmap
                    Width = source.Width;
                    Height = source.Height;

                    // get total locked pixels count
                    int PixelCount = Width * Height;

                    // Create rectangle to lock
                    Rectangle rect = new Rectangle(0, 0, Width, Height);

                    // get source bitmap pixel format size
                    Depth = System.Drawing.Bitmap.GetPixelFormatSize(source.PixelFormat);

                    // Check if bpp (Bits Per Pixel) is 8, 24, or 32
                    if (Depth != 8 && Depth != 24 && Depth != 32)
                    {
                        throw new ArgumentException("Only 8, 24 and 32 bpp images are supported.");
                    }

                    // Lock bitmap and return bitmap data
                    bitmapData = source.LockBits(rect, ImageLockMode.ReadWrite,
                                                 source.PixelFormat);

                    // create byte array to copy pixel values
                    int step = Depth / 8;
                    Pixels = new byte[PixelCount * step];
                    Iptr = bitmapData.Scan0;

                    // Copy data from pointer to array
                    Marshal.Copy(Iptr, Pixels, 0, Pixels.Length);
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }

            /// <summary>
            /// Unlock bitmap data
            /// </summary>
            public void UnlockBits()
            {
                try
                {
                    // Copy data from byte array to pointer
                    Marshal.Copy(Pixels, 0, Iptr, Pixels.Length);

                    // Unlock bitmap data
                    source.UnlockBits(bitmapData);
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }

            /// <summary>
            /// Get the color of the specified pixel
            /// </summary>
            /// <param name="x"></param>
            /// <param name="y"></param>
            /// <returns></returns>
            public Color GetPixel(int x, int y)
            {
                Color clr = Color.Empty;

                // Get color components count
                int cCount = Depth / 8;

                // Get start index of the specified pixel
                int i = ((y * Width) + x) * cCount;

                if (i > Pixels.Length - cCount)
                    throw new IndexOutOfRangeException();

                if (Depth == 32) // For 32 bpp get Red, Green, Blue and Alpha
                {
                    byte b = Pixels[i];
                    byte g = Pixels[i + 1];
                    byte r = Pixels[i + 2];
                    byte a = Pixels[i + 3]; // a
                    clr = Color.FromArgb(a, r, g, b);
                }
                if (Depth == 24) // For 24 bpp get Red, Green and Blue
                {
                    byte b = Pixels[i];
                    byte g = Pixels[i + 1];
                    byte r = Pixels[i + 2];
                    clr = Color.FromArgb(r, g, b);
                }
                if (Depth == 8)
                // For 8 bpp get color value (Red, Green and Blue values are the same)
                {
                    byte c = Pixels[i];
                    clr = Color.FromArgb(c, c, c);
                }
                return clr;
            }

            /// <summary>
            /// Set the color of the specified pixel
            /// </summary>
            /// <param name="x"></param>
            /// <param name="y"></param>
            /// <param name="color"></param>
            public void SetPixel(int x, int y, Color color)
            {
                // Get color components count
                int cCount = Depth / 8;

                // Get start index of the specified pixel
                int i = ((y * Width) + x) * cCount;

                if (Depth == 32) // For 32 bpp set Red, Green, Blue and Alpha
                {
                    Pixels[i] = color.B;
                    Pixels[i + 1] = color.G;
                    Pixels[i + 2] = color.R;
                    Pixels[i + 3] = color.A;
                }
                if (Depth == 24) // For 24 bpp set Red, Green and Blue
                {
                    Pixels[i] = color.B;
                    Pixels[i + 1] = color.G;
                    Pixels[i + 2] = color.R;
                }
                if (Depth == 8)
                // For 8 bpp set color value (Red, Green and Blue values are the same)
                {
                    Pixels[i] = color.B;
                }
            }
        }
复制代码

使用:先锁定Bitmap,然后通过Pixels操作颜色对象,最后释放锁,把数据更新到Bitmap中

复制代码
string file = @"C:\test.jpg";
            Bitmap bmp = new Bitmap(Image.FromFile(file));
            
            LockBitmap lockbmp = new LockBitmap(bmp);
            //锁定Bitmap,通过Pixel访问颜色
            lockbmp.LockBits();

            //获取颜色
            Color color = lockbmp.GetPixel(10, 10);

            //从内存解锁Bitmap
            lockbmp.UnlockBits();
复制代码

2、指针法

  这种方法访问速度比内存法更快,直接通过指针对内存进行操作,不需要进行拷贝,但是在C#中直接通过指针操作内存是不安全的,所以需要在代码中加入unsafe关键字,在生成选项中把允许不安全代码勾上,才能编译通过

  这里定义成PointerBitmap类

复制代码
public class PointBitmap
            {
                Bitmap source = null;
                IntPtr Iptr = IntPtr.Zero;
                BitmapData bitmapData = null;

                public int Depth { get; private set; }
                public int Width { get; private set; }
                public int Height { get; private set; }

                public PointBitmap(Bitmap source)
                {
                    this.source = source;
                }

                public void LockBits()
                {
                    try
                    {
                        // Get width and height of bitmap
                        Width = source.Width;
                        Height = source.Height;

                        // get total locked pixels count
                        int PixelCount = Width * Height;

                        // Create rectangle to lock
                        Rectangle rect = new Rectangle(0, 0, Width, Height);

                        // get source bitmap pixel format size
                        Depth = System.Drawing.Bitmap.GetPixelFormatSize(source.PixelFormat);

                        // Check if bpp (Bits Per Pixel) is 8, 24, or 32
                        if (Depth != 8 && Depth != 24 && Depth != 32)
                        {
                            throw new ArgumentException("Only 8, 24 and 32 bpp images are supported.");
                        }

                        // Lock bitmap and return bitmap data
                        bitmapData = source.LockBits(rect, ImageLockMode.ReadWrite,
                                                     source.PixelFormat);

                        //得到首地址
                        unsafe
                        {
                            Iptr = bitmapData.Scan0;
                            //二维图像循环
                            
                        }
                    }
                    catch (Exception ex)
                    {
                        throw ex;
                    }
                }

                public void UnlockBits()
                {
                    try
                    {
                        source.UnlockBits(bitmapData);
                    }
                    catch (Exception ex)
                    {
                        throw ex;
                    }
                }

                public Color GetPixel(int x, int y)
                {
                    unsafe
                    {
                        byte* ptr = (byte*)Iptr;
                        ptr = ptr + bitmapData.Stride * y;
                        ptr += Depth * x / 8;
                        Color c = Color.Empty;
                        if (Depth == 32)
                        {
                            int a = ptr[3];
                            int r = ptr[2];
                            int g = ptr[1];
                            int b = ptr[0];
                            c = Color.FromArgb(a, r, g, b);
                        }
                        else if (Depth == 24)
                        {
                            int r = ptr[2];
                            int g = ptr[1];
                            int b = ptr[0];
                            c = Color.FromArgb(r, g, b);
                        }
                        else if (Depth == 8)
                        {
                            int r = ptr[0];
                            c = Color.FromArgb(r, r, r);
                        }
                        return c;
                    }
                }

                public void SetPixel(int x, int y, Color c)
                {
                    unsafe
                    {
                        byte* ptr = (byte*)Iptr;
                        ptr = ptr + bitmapData.Stride * y;
                        ptr += Depth * x / 8;
                        if (Depth == 32)
                        {
                            ptr[3] = c.A;
                            ptr[2] = c.R;
                            ptr[1] = c.G;
                            ptr[0] = c.B;
                        }
                        else if (Depth == 24)
                        {
                            ptr[2] = c.R;
                            ptr[1] = c.G;
                            ptr[0] = c.B;
                        }
                        else if (Depth == 8)
                        {
                            ptr[2] = c.R;
                            ptr[1] = c.G;
                            ptr[0] = c.B;
                        }
                    }
                }
            }
复制代码

使用方法这里就不列出来了,跟上面的LockBitmap类似

 

本文实例讲述了C#对图片文件的压缩、裁剪操作方法,在C#项目开发中非常有实用价值。分享给大家供大家参考。具体如下:

一般在做项目时,对图片的处理,以前都采用在上传时,限制其大小的方式,这样带来诸多不便。毕竟网站运维人员不一定会对图片做处理,经常超出大小限制,即使会使用图片处理软件的,也由于个人水平方面原因,处理效果差强人意。

于是采用C#为我们提供的图像编辑功能,实现一站式上传,通过程序生成所需大小、尺寸的目标图片。

具体步骤如下:

先说图片压缩:

第一步:需要读取一个图片文件,读取方法:

// <param name="ImageFilePathAndName">图片文件的全路径名称</param> 
public Image ResourceImage =Image.FromFile(ImageFilePathAndName); 

说明:

Image类:引用自System.Drawing,为源自 Bitmap 和 Metafile 的类提供功能的抽象基类。

主要属性:Size->获取此图像的以像素为单位的宽度和高度。

PhysicalDimension->获取此图像的宽度和高度(如果该图像是位图,以像素为单位返回宽度和高度。如果该图像是图元文件,则以0.01 毫米为单位返回宽度和高度。)。

PixelFormat->获取此 Image 的像素格式。

Height、Width->获取此 Image 的高度、宽度(以像素为单位)。

主要方法:FromFile(String)->从指定的文件创建 Image。

FromStream(Stream)->从指定的数据流创建 Image。

Save(String fileName)->将该 Image 保存到指定的文件或流。

Save(Stream, ImageFormat)->将此图像以指定的格式保存到指定的流中。

Save(String, ImageFormat)->将此 Image 以指定格式保存到指定文件。

更多属性和方法说明请点击。

第二步,生成缩略图,并且将原图内容按指定大小绘制到目标图片

复制代码
/// <summary> 
/// 生成缩略图重载方法1,返回缩略图的Image对象 
/// </summary> 
/// <param name="Width">缩略图的宽度</param> 
/// <param name="Height">缩略图的高度</param> 
/// <returns>缩略图的Image对象</returns> 
public Image GetReducedImage(int Width, int Height) 
{ 
  try
  { 
 //用指定的大小和格式初始化Bitmap类的新实例 
 Bitmap bitmap = new Bitmap(Width, Height, PixelFormat.Format32bppArgb); 
 //从指定的Image对象创建新Graphics对象 
 Graphics graphics = Graphics.FromImage(bitmap); 
 //清除整个绘图面并以透明背景色填充 
 graphics.Clear(Color.Transparent); 
 //在指定位置并且按指定大小绘制原图片对象 
 graphics.DrawImage(ResourceImage, new Rectangle(0, 0, Width, Height)); 
 return bitmap; 
  } 
  catch (Exception e) 
  { 
 ErrMessage = e.Message; 
 return null; 
  } 
} 
复制代码

说明:

1、Bitmap类

引用自System.Drawing,封装 GDI+ 位图,此位图由图形图像及其特性的像素数据组成。Bitmap 是用于处理由像素数据定义的图像的对象。

关于封装图像的对象,详细介绍可参看官方文档:http://msdn.microsoft.com/zh-cn/library/system.drawing.bitmap.aspx

2、Graphics类

引用自System.Drawing,(处理图像的对象),封装一个 GDI+ 绘图图面。

关于Graphics类可点此查看官方教程:http://msdn.microsoft.com/zh-cn/library/system.drawing.graphics.aspx

第三步,保存

第二步操作中返回的Image对象,暂时命名为:iImage:

iImage.Save(pathAndName, System.Drawing.Imaging.ImageFormat.Jpeg); 

以上是压缩操作,做了下试验,101k的图片,经过压缩后是57k。这个应该和尺寸有关系。

以下是图片裁剪,其实原理和上面相似,无非也就是对图片进行重画操作。

复制代码
/// <summary> 
/// 截取图片方法 
/// </summary> 
/// <param name="url">图片地址</param> 
/// <param name="beginX">开始位置-X</param> 
/// <param name="beginY">开始位置-Y</param> 
/// <param name="getX">截取宽度</param> 
/// <param name="getY">截取长度</param> 
/// <param name="fileName">文件名称</param> 
/// <param name="savePath">保存路径</param> 
/// <param name="fileExt">后缀名</param> 
public static string CutImage(string url, int beginX, int beginY, int getX, int getY, string fileName, string savePath, string fileExt) 
{ 
  if ((beginX < getX) && (beginY < getY)) 
  { 
 Bitmap bitmap = new Bitmap(url);//原图 
if (((beginX + getX) <= bitmap.Width) && ((beginY + getY) <= bitmap.Height)) 
 { 
   Bitmap destBitmap = new Bitmap(getX, getY);//目标图 
   Rectangle destRect = new Rectangle(0, 0, getX, getY);//矩形容器 
   Rectangle srcRect = new Rectangle(beginX, beginY, getX, getY); 
 
   Graphics.FromImage(destBitmap); 
            Graphics.DrawImage(bitmap, destRect, srcRect, GraphicsUnit.Pixel); 
     
   ImageFormat format = ImageFormat.Png; 
   switch (fileExt.ToLower()) 
   { 
 case "png": 
   format = ImageFormat.Png; 
   break; 
 case "bmp": 
   format = ImageFormat.Bmp; 
   break; 
 case "gif": 
   format = ImageFormat.Gif; 
   break; 
   } 
   destBitmap.Save(savePath + "//" + fileName , format); 
   return savePath + "\\" + "*" + fileName.Split('.')[0] + "." + fileExt; 
 } 
 else
 { 
   return "截取范围超出图片范围"; 
 } 
  } 
  else
  { 
 return "请确认(beginX < getX)&&(beginY < getY)"; 
  } 
} 
复制代码

说明:

Rectangle类:矩形,详情可参考官方文档:http://msdn.microsoft.com/zh-cn/library/system.windows.shapes.rectangle(v=vs.85).aspx