标签归档CLR

DotNet加密方法分析–数字签名

   
马上就要过年回村里了,村里没wifi,没有4G,没有流量,更加要之是过几上电脑就得卖掉换车票了,得抢写几首博客。

澳门新葡亰官网 1

   
数据安全的连锁技术以如今更进一步变得要,因为人们对于我的音信还起同一种保护之欲念,不思量让人取到自己之私密信息,加密几乎就是这时代之重大词了。在此HTTPS盛行的时期,作为一个开发人员怎么可能无失探听与读书呢。这首博文就来为大家简单介绍一个HTTPS在.NET种的使用和贯彻方式。

   
数字证书和数字签名的落实重大是依据不对如加密暨数字摘要,数字签名是数字证书不可或缺的一致有的。这首博客主要教学数字签名、数字证书,以及数字签名在.NET种的实现方式。

一.数字签名概述:

   1.数字签名的基本原理:

     
这里首先来询问部分哟叫数字签名,数字签名是增大以数单元上的有的数目,或是对数码单元所召开的密码变换。数字签名是针对性未对如加密及信摘要的运用。数签名的规律:使用不对如密钥将签约函数添加到无对称算法,创建一个“签名”,另一样方接受加密的信,使用确认函数来证实签名。有如下图:

澳门新葡亰官网 2

   
 说明:用户A选择一个不对如签名算法创建同对准新密钥,自己保留私钥,公钥发给B。用户B使用用户A的公钥来说明签名。

     将破列码做吗创建数字签名,有如下图:

澳门新葡亰官网 3

    将辟列码作为确认一个数字签名,有如下图:

澳门新葡亰官网 4

    2.数字签名的特点:

     
第三着无克伪造用户A的数字签名;第三正值不能够更使用用户A的数字签名;第三正无克改签名后的文本;用户A无法否认自己之签名文件。数字签名能够提供平等种植与物理签名类似的合理编制。数字签名的安全性与加密底其它方是平等的,他们还是因可能的中密钥管理的。数字签名只行使了不对称密钥加密算法,能确保发送信息的完整性、身份验证和莫可以矢口否认实施,数字加密应用了针对性称密钥加密算法和不对称密钥加密算法相结合的主意,能够确保发送信息的保密性。

二.数字证书概述:

   对于HTTPS(Hyper Text Transfer Protocol over Secure Socket
Layer)很多开发人员都无见面生,即使是普通用户也是于的耳熟能详。数字证书(公钥证书):用于电子信息活动受到电子文件行为主体的辨证和说明,并可实现电子文本保密性和完整性的电子数据。数字证书是一个通过证书认证中心批发的证明。

 
 数字证书:个人数字证书,单位数字证书、单位职工数字证书、服务器证书、VPN证书、WAP证书、代码签名证书及表单签名证书等。

 
 数字证书是一个透过证书授权重心数字签名的含公开密钥拥有者信息与公开密钥的文书,最简便易行的证件包含一个公开密钥、名称一剂证书授权中心的数字签名。

 
 数字证书的性状:信息之保密性;交易者身份的阳;不可否认性、不可修改性。

 
 数字证书的老三栽保存形式:带有私钥的证件;二上前制编码的关系;Base64编码证书。

三.DotNet数字签名核心目标解析:

   
 在.NET中隐含两种植支持数字签名的非对称算法:RSA算法(为有限栽多少加密和数字签名定义了函数);DSA算法(支持数字签名,不支持数据加密)。在.NET中应用RSA算法进行数字签名使用RSACryptoServiceProvider类,使用DSA进行数字签名的季只基本类设下图:

澳门新葡亰官网 5

 
 DSA类:数字签名算法DSA的基类;DSACryptoServiceProvider类:定义访问DSA算法的加密服务提供程序实现的包裹对象;DSASignatureDeformatter类:验证DSA签名;DSASignatureFormatter类:创建DSA签名;

   接下来我们具体了解一下这些类似:

     1.RSACryptoServiceProvider类:

       
(1).SignData()方法:使用指定的哈希算法计算指定输入流的哈希值,并针对性计量所得的哈希值签名。

public byte[] SignData(Stream inputStream, object halg)
    {
      int calgHash = Utils.ObjToAlgId(halg, OidGroup.HashAlgorithm);
      return this.SignHash(Utils.ObjToHashAlgorithm(halg).ComputeHash(inputStream), calgHash);
    }

   
 该办法有三独重载方法,三个重载方法的首先单参数不同,分别是Stream、byte[]星星单门类。由代码可以观看,该措施接受两独参数,inputStream是如果算其哈希值的输入数据,halg用于创造哈希值的哈希算法。SignHash()通过用私钥对那进展加密来计量指定哈希值的签。

       
(2).VerifyData():通过运用提供的公钥确定签名中之哈希值并将其与所提供数据的哈希值进行比印证数字签名是否行得通。

 public bool VerifyData(byte[] buffer, object halg, byte[] signature)
    {
      int calgHash = Utils.ObjToAlgId(halg, OidGroup.HashAlgorithm);
      return this.VerifyHash(Utils.ObjToHashAlgorithm(halg).ComputeHash(buffer), calgHash, signature);
    }

   
该措施没有重载版本,有源码可以见见该方式接收三独参数,分别是:buffer已签约的数据,halg用于创造数量的哈希值的哈希算法名称,signature要证实的签名数据。该方式返回一个布尔品种,如果签名中,则也
true;否则也
false。VerifyHash()通过下提供的公钥确定签名中之哈希值并以其及提供的哈希值进行比较来说明数字签名是否管用。

   2.DSA类解析:

     (1).CreateSignature():创建指定数量的 Cryptography.DSA 签名。

 public abstract byte[] CreateSignature(byte[] rgbHash);

   
 该方法吗一个虚幻方法,在派生类吃重写,接受一个字节数组表示要签的数量,返回指定数量的数字签名。在用CreateSignature方法时,必须协调创办SHA-1散列码,返回一个用字节数组表示的DSA签名。

     (2).VerifySignature():验证指定数量的 Cryptography.DSA 签名。

public abstract bool VerifySignature(byte[] rgbHash, byte[] rgbSignature);

     该方法接受字符数组表示的SHA-1散列码和签名来证明。

    3.DSACryptoServiceProvider类解析:

     (1).ImportParameters():导入指定的
DSAParameters。该方式接受一个参数,Cryptography.DSA的参数。

   
 (2).VerifyData():通过以点名的签字数据以及为指定数量测算的署名进行较来证明指定的签署数据。

 public bool VerifyData(byte[] rgbData, byte[] rgbSignature)
    {
      return this.VerifyHash(this._sha1.ComputeHash(rgbData), (string) null, rgbSignature);
    }

     
该措施接受两只参数,rgbData曾签署的多寡;rgbSignature要验证的署名数据,如果签名验证为可行,则为
true;否则,为
false。VerifyHash()通过将指定的签约数据及为指定哈希值计算的签名进行比较来说明指定的署名数据,我们看一下VerifyHash()的贯彻代码:

 public bool VerifyHash(byte[] rgbHash, string str, byte[] rgbSignature)
    {
      if (rgbHash == null)
        throw new ArgumentNullException("rgbHash");
      if (rgbSignature == null)
        throw new ArgumentNullException("rgbSignature");
      int calgHash = X509Utils.NameOrOidToAlgId(str, OidGroup.HashAlgorithm);
      if (rgbHash.Length != this._sha1.HashSize / 8)
      {
        string key = "Cryptography_InvalidHashSize";
        object[] objArray = new object[2];
        int index1 = 0;
        string str1 = "SHA1";
        objArray[index1] = (object) str1;
        int index2 = 1;
        // ISSUE: variable of a boxed type
        __Boxed<int> local = (ValueType) (this._sha1.HashSize / 8);
        objArray[index2] = (object) local;
        throw new CryptographicException(Environment.GetResourceString(key, objArray));
      }
      this.GetKeyPair();
      return Utils.VerifySign(this._safeKeyHandle, 8704, calgHash, rgbHash, rgbSignature);
    }

   
 该方法接收三只参数,rgbHash要签名的数目的哈希值,str用于创造数量的哈希值的哈希算法名称,rgbSignature要证明的签数据。

    4.X509Certificate类解析:

       
该类在System.Security.Cryptography.X509Certificates空间下,提供协助您利用
X.509 v.3 证书的法。

      (1).LoadCertificateFromBlob():加载证书:

private void LoadCertificateFromBlob(byte[] rawData, object password, X509KeyStorageFlags keyStorageFlags)
    {
      if (rawData == null || rawData.Length == 0)
        throw new ArgumentException(Environment.GetResourceString("Arg_EmptyOrNullArray"), "rawData");
      if (X509Utils.MapContentType(X509Utils._QueryCertBlobType(rawData)) == X509ContentType.Pfx && (keyStorageFlags & X509KeyStorageFlags.PersistKeySet) == X509KeyStorageFlags.PersistKeySet)
        new KeyContainerPermission(KeyContainerPermissionFlags.Create).Demand();
      uint dwFlags = X509Utils.MapKeyStorageFlags(keyStorageFlags);
      IntPtr num = IntPtr.Zero;
      RuntimeHelpers.PrepareConstrainedRegions();
      try
      {
        num = X509Utils.PasswordToHGlobalUni(password);
        X509Utils._LoadCertFromBlob(rawData, num, dwFlags, (keyStorageFlags & X509KeyStorageFlags.PersistKeySet) != X509KeyStorageFlags.DefaultKeySet, ref this.m_safeCertContext);
      }
      finally
      {
        if (num != IntPtr.Zero)
          Marshal.ZeroFreeGlobalAllocUnicode(num);
      }
    }

   该办法是X509Certificate类构造函数等几乎单办法加载证书之切切实实贯彻方式。

  澳门新葡亰官网    (2).Export():使用指定的格式和密码将眼前
X509Certificate对象导出到字节数组。

 public virtual byte[] Export(X509ContentType contentType, SecureString password)
    {
      return this.ExportHelper(contentType, (object) password);
    }

        该方法接受两只参数,contentType描述如何设置输出数据格式的
X509ContentType 值之一。password访问 X.509
证书数据所需要的密码。返回表示目前 X509Certificate 对象的字节数组。

四.DotNet数字签名实例:

    下面提供一个X509Certificate的操作方法实例:

  public void EncryptXmlDocument(string arqXmlAssinar, string tagAssinatura, string tagAtributoId, X509Certificate2 x509Cert)
        {
            StreamReader sr = null;
            try
            {
                sr = System.IO.File.OpenText(arqXmlAssinar);
                var xmlString = sr.ReadToEnd();
                sr.Close();
                sr = null;
                XmlDocument doc = new XmlDocument { PreserveWhitespace = false };
                doc.LoadXml(xmlString);
                if (doc.GetElementsByTagName(tagAssinatura).Count == 0)
                {
                    throw new Exception(tagAssinatura.Trim());
                }
                if (doc.GetElementsByTagName(tagAtributoId).Count == 0)
                {
                    throw new Exception(tagAtributoId.Trim());
                }
                XmlNodeList lists = doc.GetElementsByTagName(tagAssinatura);
                foreach (XmlNode nodes in lists)
                {
                    foreach (XmlNode childNodes in nodes.ChildNodes)
                    {
                        if (!childNodes.Name.Equals(tagAtributoId))
                            continue;
                        if (childNodes.NextSibling != null && childNodes.NextSibling.Name.Equals("Signature"))
                            continue;
                        Reference reference = new Reference { Uri = "" };                                 
                        XmlElement childElemen = (XmlElement)childNodes;
                        if (childElemen.GetAttributeNode("Id") != null)
                        {
                            var attributeNode = childElemen.GetAttributeNode("Id");
                            if (attributeNode != null)
                                reference.Uri = "#" + attributeNode.Value;
                        }
                        else if (childElemen.GetAttributeNode("id") != null)
                        {
                            var attributeNode = childElemen.GetAttributeNode("id");
                            if (attributeNode != null)
                                reference.Uri = "#" + attributeNode.Value;
                        }
                        XmlDocument documentoNovo = new XmlDocument();
                        documentoNovo.LoadXml(nodes.OuterXml);
                        SignedXml signedXml = new SignedXml(documentoNovo) { SigningKey = x509Cert.PrivateKey };
                        XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
                        reference.AddTransform(env);
                        XmlDsigC14NTransform c14 = new XmlDsigC14NTransform();
                        reference.AddTransform(c14);
                        signedXml.AddReference(reference);
                        KeyInfo keyInfo = new KeyInfo();
                        keyInfo.AddClause(new KeyInfoX509Data(x509Cert));
                        signedXml.KeyInfo = keyInfo;
                        signedXml.ComputeSignature();
                        XmlElement xmlDigitalSignature = signedXml.GetXml();
nodes.AppendChild(doc.ImportNode(xmlDigitalSignature, true));
                    }
                }
                var xmlDoc = doc;
                var stringXmlAssinado = xmlDoc.OuterXml;
                StreamWriter sw2 = System.IO.File.CreateText(arqXmlAssinar);
                sw2.Write(stringXmlAssinado);
                sw2.Close();
            }
            catch (CryptographicException ex)
            {
                throw new CryptographicException(ex.Message);
            }
            catch (Exception e)
            {
                throw new Exception(e.Message);
            }
            finally
            {
                if (sr != null) sr.Close();
            }
        }

五.总结:

 
 上面是出关.NET数字证书的概括介绍,如发生描绘的不规则的地方还望多多包涵,在博文被产生些类和办法没有比较多之罗列出,有趣味之得友善失去深入的询问。我们上学一个学问时,已经起知识之布局了解开始,这样有利于我们站在全局思考问题。

 

加密算法系列:

     
 DotNet加密方法分析–散列加密:http://www.cnblogs.com/pengze0902/p/6268700.html

     
 DotNet加密方法分析–对如加密:http://www.cnblogs.com/pengze0902/p/6268702.html

     
 DotNet加密方法分析–数字签名:http://www.cnblogs.com/pengze0902/p/6268709.html

     
 DotNet加密方法分析–非对如加密:http://www.cnblogs.com/pengze0902/p/6268705.html

关于.NET参数传递方式的琢磨

 
 年关走近,整个人既远非了劳作与写作的豪情,估计这时节多人口以及自家基本上,该接近的知心,该聚会饮酒的团圆饭喝酒,总之就是没有了劳作的思想(我出很多设法,但尽管是吃无动自己的小动作,所以自己只得看在别人当召开自我想做的从业,吃我思吃的事物。)。本人出于上个月之每周四五篇,到今天底篇章缩短到每周一篇,说个实话,现在之一样首也时有发生不思写的动机了(这同首还是咬在牙写的,感觉实在是形容不动了,写博客太折腾人矣,谁写哪个知道呀!),不过要想写出来好帮助到大家,如产生描绘的欠缺的地方,还为大家多多指正,知识在总结和自省,对别人呢本着好都是一个增高。
 

 
 这里先来同样截废话,缓和一下氛围,免得吃大家十分窘迫(太直白了要未顶好,总不能够来看好的女生就表白吧,还得多么的处,让丁觉着你沉稳有深度。),现在进来我们今天底博客内容,那就是.NET的参数用法。因为在.NET的参数用法及约束特别多,对于许多初家的话,这样丰富多彩的参数用户简直就是跟扯淡一样,即使对于是持有丰富经验的开发者来说,也不一定能生自在利用具有的参数用法及抉择适合的参数类型。谈到参数,估计很多人即使只是怀念在咱以相似的方调用中利用的那么,如string,int,object等等类型,更多的也便不曾了记忆,就是明,也就算是于遇到了重复去查看转,这样实在也远非错,毕竟非能够话费过多的时刻因此当怎么不常用的学问上,但是自个人觉得对文化或者待提前有一个到家的读,可能实际的细节无能够生好之把,但是于全局的定义还是得起一个整的攻。

 
 下面就是简单的介绍一下.NET之有的常用参数用法,如发欠缺还望指正,也接大家以脚留言讨论,分享自己的观。

一.DotNet参数概述:

   
.NET中参数(形式参数)变量是艺术还是索引器声明的均等组成部分,而实参是调用方法或者索引器时利用的表达式。

   
在CLR中,默认的情况下拥有的法子参数还是传值的。在传递引用类型的对象时,对一个目标的援会传送给艺术。这里的船引用我是为传值的方传为艺术的。这也代表方法能够修改对象,而调用者能顾这些修改。对于值类型的实例,传为艺术的实例的一个副本。意味着方法将赢得其专用的一个值类型实例副本,调用者中的实例不给影响。

   
在CLR中允许以污染引用而未传值的点子传递参数,在C#遭使用out和ref来实现传递引用的章程传值。在C#着运用out和ref来实现传递引用的措施传值,这片单举足轻重字告诉编译器生成元数据来指明该参数是招引用的,编译器将转代码来传递参数的地址,而非是传递参数本身。为值类型使用out和ref,效果同样于为传值的主意传递引用类型。 
 

    常用之参数主要有基本型参数,泛型参数,以及<in T>和<out
T>,dynamic等等。例如<in T>和<out
T>,在CLR中支持泛型类型的可变性,C#以4.0时取得了性命泛型遍体所不可不的语法,并且现在编译器也能亮接口和信托可能的换。可变性是坐相同种植类型安全的计,讲一个目标作为任何一个靶来使。可变性应用被泛型接口和泛型委托的色参数中。协变形用于为调用者返回某项操作的价值;逆变性是赖调用者想API传入值;不变性是相对于协变性和逆变性,是借助什么啊非会见起。对于当下面的文化很之长,有趣味之得自动了解,这里就是非开详细的牵线了。dynamic类型,C#举凡一律帮派静态类型的语言,在少数情况下,C#编译器要找特定的名号而休是接口。dynamic可以于编译时举行其他事,到实施时更由框架进行拍卖。有关动态类型的牵线也非开还深入的介绍。

   
在.NET中参数的运用方式要也而卜参数、命名参数、可更换多少参数等等。本文下面为是至关重要介绍就三种植参数的以方法。

二.DotNet参数用法:

   
以下是重要介绍三种参数的用法:可选参数;命名实参;传递可转换多少之参数。
  

   1.而卜参数:

     (1).基本用法:

       
如果某操作需要多单价值,而发生来值当历次调用的时候以屡次是平之,这时便可以使用可选参数。在C#先实现而换参数的功能,往往声明一个分包有或参数的办法,其他方式调用这个方法,并传递恰当的默认值。

       
在可选参数中,设计一个方式的参数时,可以为一些或整参数私分配默认值。在调用这些主意代码可以选取不指定部分实参,接受默认值。还好在调用方法时,还可由此点名参数名称的方法吗那个传递实参。如下实例:

        static void OptionalParameters(int x, int y = 10, int z = 20)
        {
            Console.WriteLine("x={0} y={1} z={2}",x,y,z);
        }

         OptionalParameters(1, 2, 3);
         OptionalParameters(1, 2);
         OptionalParameters(1);

     以上之例子可以生知的相该之所以法,int y=10及int
z=20随即片单参数就是可选参数。可摘参数的用中,如果调用时大概了一个参数,C#编译器会自动嵌入参数的默认值。向方传递实参时,编译器按自漏洞百出为右侧的一一对实参进行求值。使用已命名的参数传递实参时,编译器仍然据从左到右的依次对实参进行求值。

      (2).基本条件:

       可选取参数包含有正式,具体的一部分渴求如下:

    (a).所有可选参数必须出现于必要参数后,参数数组(使用params修饰符声明)除外,但她们要出现在参数列表的尾声,在她们前面是可选参数。

    (b).参数数组不可知声称也可摘的,如果调用者没有点名值,将下空数组代替。

    (c).可选取参数不能够使用ref和out修饰符。

    (d).可挑选参数可以吗其它项目,但对此指定的默认值却发生部分范围,那就是默认值必须也常量(数字要字符串字面量、null、const成员、枚举成员、default(T)操作符)。

    (e).指定的值会隐式转换为参数类型,但是这种转移不克是用户定义之。

    (f).可以呢计、构造器、有参属性的参数指定默认值,还好啊属于委托定有的参数指定默认值。

    (g).C#未同意省略逗号之间的实参。

     
在用可卜参数时,对于引用类型应用null来做默认值,如果参数类型是值类型,只需要动用相应的可空值类型作为默认值。

      (3).代码示例:

        /// <summary>
        /// 提取异常及其内部异常堆栈跟踪
        /// </summary>
        /// <param name="exception">提取的例外</param>
        /// <param name="lastStackTrace">最后提取的堆栈跟踪(对于递归), String.Empty or null</param>
        /// <param name="exCount">提取的堆栈数(对于递归)</param>
        /// <returns>Syste.String</returns>
        public static string ExtractAllStackTrace(this Exception exception, string lastStackTrace = null, int exCount = 1)
        {
            while (true)
            {
                var ex = exception;
                const string entryFormat = "#{0}: {1}\r\n{2}";
                lastStackTrace = lastStackTrace ?? string.Empty;
                lastStackTrace += string.Format(entryFormat, exCount, ex.Message, ex.StackTrace);
                if (exception.Data.Count > 0)
                {
                    lastStackTrace += "\r\n    Data: ";
                    lastStackTrace = exception.Data.Cast<DictionaryEntry>().Aggregate(lastStackTrace, (current, entry) => current + $"\r\n\t{entry.Key}: {exception.Data[entry.Key]}");
                }
                //递归添加内部异常
                if ((ex = ex.InnerException) == null) return lastStackTrace;
                exception = ex;
                lastStackTrace = $"{lastStackTrace}\r\n\r\n";
                exCount = ++exCount;
            }
        }

   2.命名实参:

       
 以上讲解了可选参数的一对基本概念和用法,接下去看一下命名参数的连带操作用法:

      (1).基本用法:

         
命名实参是乘在指定实参的价值时,可以同时指定相应的参数名称。编译器将判断参数的名称是否正确,并以点名的值赋给这参数。命名参数在一一实参之前增长它的参数名称以及一个冒号。如下代码:

new StreamWriter(path:filename,aooend:true,encoding:realEncoding);

 如果假定针对含有ref和out的参数指定名称,需要将ref和out修饰符放在名称后,实参之前。

int number;
bool success=int.TryParse("10",result:out number);

      (2).基本尺度:

       
在命名参数中,所有的命名参数必须放在位置实参之后,两者之间的职务不克改变。位置实参总是指于方声明遭相应的参数,不克超过了参数后,在经命名相应岗位的实参来指定。实参仍然按编制顺序求值,即使是顺序来或会见不同让参数的扬言顺序。

       
在相似情况下,可摘参数与命名实参会并配合以。可挑选参数会多适用方法的多少,而命名实参会减少用办法的多寡。为了检查是否是一定的适用方法,编译器会用位置参数的次第构建一个扩散实参的列表,然后针对命名实参和多余的参数进行匹配。如果没有点名某个必备参数,或某命名实参不能够和剩余的参数相配合,那么这艺术就是不是适用的。

     
 命名实参有时可以代表强制转换,来帮衬编译器进行重载决策。如果方式是由模块的标调用的,更改参数的默认值是颇具神秘的惊险的。可以随号将实参传给没有默认值的参数,但是编译器要想编译代码,所有要求的实参都要传递。

      
在写C#代码和COM对象模型进行互操作时,C#的可选参数与命名参数功能是极端好用底,调用一个COM组件时,为了为污染引用的方传送一个实参,C#还允许探视略REF/OUT,在嗲用COM组件时,C#要求得向实参应用OUT.REF关键字。 
  

   3.传递可易多少之参数:

     
在品种开发中,有时我们得定义一个法来获取可转移多少之参数。可以动用params,params只能采用被艺术签名中之尾声一个参数。params关键字告诉编译器向参数应用System.ParamArrayAttribute的实例。我们切实看一下贯彻之代码:

[AttributeUsage(AttributeTargets.Parameter, Inherited=true, AllowMultiple=false), ComVisible(true), __DynamicallyInvokable]
public sealed class ParamArrayAttribute : Attribute
{
    // Methods
    [__DynamicallyInvokable]
    public ParamArrayAttribute();
}


[__DynamicallyInvokable]
public ParamArrayAttribute()
{
}

   
 以上的代码可以看出该类继承自Attribute类,对于Attribute类可能无见面生,那就是是概念定制性的基类,说明ParamArrayAttribute类用于定义定制性,ParamArrayAttribute类在System命名空间下,ParamArrayAttribute类只发生一个构造方法,没有现实的兑现。AttributeUsage也定义了性能的运用方式。

   
C#编译器检测及一个方调用时,会检讨有拥有指定名称、同时参数没有采取ParamArrayAttribute的方法。如果找到一个配合的法子,编译器生成调用它所欲的代码。如果编译器没有找到一个郎才女貌的主意,会一直检查采取ParamArrayAttribute的法门。如果找到一个配合的方,编译器会士化作代码来组织一个数组,填充它的素,再生成代码来调用选定的措施。

   
调用一个参数数量可变的方式时,会造成局部分外的属性损失,数组对象要于针对达标分红,数组元素必须初始化,而且屡组的内存最终必须垃圾回收。

    提供一个主意代码,仅供参考:

        /// <summary>
        /// 字符型二维数组转换成DataTable 
        /// </summary>
        /// <param name="stringDyadicArray"></param>
        /// <param name="messageOut"></param>
        /// <param name="dataTableColumnsName"></param>
        /// <returns></returns>
        public DataTable DyadicArrayToDataTable(string[,] stringDyadicArray, out bool messageOut,
            params object[] dataTableColumnsName)
        {
            if (stringDyadicArray == null)
            {
                throw new ArgumentNullException("stringDyadicArray");
            }
            var returnDataTable = new DataTable();
            if (dataTableColumnsName.Length != stringDyadicArray.GetLength(1))
            {
                messageOut = false;
                return returnDataTable;
            }
            for (var dataTableColumnsCount = 0;dataTableColumnsCount < dataTableColumnsName.Length;dataTableColumnsCount++)
            {
                returnDataTable.Columns.Add(dataTableColumnsName[dataTableColumnsCount].ToString());
            }
            for (var dyadicArrayRow = 0; dyadicArrayRow < stringDyadicArray.GetLength(0); dyadicArrayRow++)
            {
                var addDataRow = returnDataTable.NewRow();
                for (var dyadicArrayColumns = 0; dyadicArrayColumns < stringDyadicArray.GetLength(1);dyadicArrayColumns++)
                {
                    addDataRow[dataTableColumnsName[dyadicArrayColumns].ToString()] = stringDyadicArray[dyadicArrayRow, dyadicArrayColumns];
                }
                returnDataTable.Rows.Add(addDataRow);
            }
            messageOut = true;
            return returnDataTable;
        }

  
以上被出了一个以可变换参数数量以及命名参数的使样例,完成了用二维字节数组转化为DataTable对象,将数组进行遍历,并拿数组写副datatable中,对于任何方的逻辑就是无开深入介绍,代码比较的简。

三.及参数有关的部分指导标准:

    声明方法的参数类型时,应尽量指定最弱的类,最好是接口而非是基类。

   
在设计模式的着力条件被,迪米特法则为比最少知标准化,迪米特法则是依如果少只八九不离十不肯定彼此直接通信,那么这半独像样就不应直接的相互作用。如果内部一个近似需要调用另一个接近的之一一个方法吧,可以经外人转发此调用。在近似组织的统筹上,每一个类都应尽量降低成员的访问权限。类中的耦合度越弱,越便宜复用,一个处弱耦合的近乎吃修改,不会见对发出涉嫌之切近造成波及。

   
对于参数的下受到,我们于针对参数类型的应用上,还是要好细致跟认真的错过考虑,因为当参数类型的概念及,在早晚水准上影响着我们先后的扩展性和稳定,如果参数类型的牢笼比较大,对于后续措施的壮大,意义是惊天动地的。在任何面向对象的言语系统受到,一切设计模式都是出于“多态”延伸而来,对于接口和嘱托都是当咱们面向对象设计受到动用过多底,目的较多之凡在利用时扩大参数的约束性。

   
在点子的回值类型中,返回的类应该声明也极其强之档次,以免受限于特定的品类。

四.总结:

 
 以上是一模一样首简单介绍道参数的稿子,在文章内容中要对介绍可选参数、命名参数等。以上之情而发欠缺之地方还望大家多多原谅,也可望会指出对应之题目。知识先于模范,后于反思。学习了一点继,需要我们去总结与反省,其中的内涵我们才见面生出日以及精力,以及由于能力去想。

个人档案DotNet加密方法分析–数字签名

   
马上将过年回村里了,村里没wifi,没有4G,没有流量,更加重大之是喽几天电脑便得卖掉换车票了,得赶紧写几首博客。

个人档案 1

   
数据安全之连带技能在现今愈加变得要,因为人们对自己之音都发出雷同栽保护的欲望,不思叫人得到好的私密信息,加密几已是这个时代之严重性词了。在这HTTPS盛行的时期,作为一个开发人员怎么可能无失探听与上学也。这首博文就来深受大家简单介绍一个HTTPS在.NET种的行使和兑现方式。

   
数字证书和数字签名的落实重大是根据不对如加密跟数字摘要,数字签名是数字证书不可或缺的如出一辙片段。这首博客主要教授数字签名、数字证书,以及数字签名在.NET种之实现方式。

一.数字签名概述:

   1.数字签名的基本原理:

     
这里首先来询问部分哟叫做数字签名,数字签名是外加以数单元上之有些数量,或是对数码单元所举行的密码变换。数字签名是本着无对如加密跟信息摘要的动。数签名的原理:使用不对如密钥将签署函数添加至非对称算法,创建一个“签名”,另一样在接受加密的信息,使用确认函数来证明签名。有如下图:

个人档案 2

   
 说明:用户A选择一个未对如签名算法创建同针对新密钥,自己保留私钥,公钥发给B。用户B使用用户A的公钥来证实签名。

     将解除列码做也创造数字签名,有如下图:

个人档案 3

    将破列码作为确认一个数字签名,有如下图:

个人档案 4

    2.数字签名的性状:

     
第三在不能够顶用户A的数字签名;第三着无克重复以用户A的数字签名;第三正值未可知更改签名后的文件;用户A无法否认自己之签名文件。数字签名能够提供相同栽及大体签名类似之合理性编制。数字签名的安全性和加密之别样地方是均等的,他们都是依据可能的有用密钥管理之。数字签名只利用了未对称密钥加密算法,能确保发送信息的完整性、身份证明和莫可以矢口否认实施,数字加密应用了针对性称密钥加密算法和免对称密钥加密算法相结合的艺术,能够确保发送信息的保密性。

二.数字证书概述:

   对于HTTPS(Hyper Text Transfer Protocol over Secure Socket
Layer)很多开发人员都不见面生,即使是普通用户也是较的耳熟能详。数字证书(公钥证书):用于电子信息活动受到电子文本行为主体的证实和说明,并不过实现电子文件保密性和完整性的电子数据。数字证书是一个通过证书认证中心批发的关系。

 
 数字证书:个人数字证书,单位数字证书、单位员工数字证书、服务器证书、VPN证书、WAP证书、代码签名证书及表单签名证书等。

 
 数字证书是一个经证书授权重心数字签名的含有公开密钥拥有者信息以及公开密钥的公文,最简易的证书包含一个公开密钥、名称一剂证书授权中心的数字签名。

 
 数字证书的特性:信息的保密性;交易者身份的家喻户晓;不可否认性、不可修改性。

 
 数字证书的老三栽保存形式:带有私钥的证书;二进制编码的证明;Base64编码证书。

三.DotNet数字签名核心目标解析:

   
 在.NET中蕴藏两种支持数字签名的非对称算法:RSA算法(为寡栽多少加密和数字签名定义了函数);DSA算法(支持数字签名,不支持数据加密)。在.NET中采取RSA算法进行数字签名使用RSACryptoServiceProvider类,使用DSA进行数字签名的季单中心类设下图:

个人档案 5

 
 DSA类:数字签名算法DSA的基类;DSACryptoServiceProvider类:定义访问DSA算法的加密服务提供程序实现的包装对象;DSASignatureDeformatter类:验证DSA签名;DSASignatureFormatter类:创建DSA签名;

   接下来我们切实了解一下这些类似:

     1.RSACryptoServiceProvider类:

       
(1).SignData()方法:使用指定的哈希算法计算指定输入流的哈希值,并针对性计量所得的哈希值签名。

public byte[] SignData(Stream inputStream, object halg)
    {
      int calgHash = Utils.ObjToAlgId(halg, OidGroup.HashAlgorithm);
      return this.SignHash(Utils.ObjToHashAlgorithm(halg).ComputeHash(inputStream), calgHash);
    }

   
 该措施有三独重载方法,三个重载方法的率先单参数不同,分别是Stream、byte[]少个档次。由代码可以见到,该方式接受两单参数,inputStream是一旦算其哈希值的输入数据,halg用于创造哈希值的哈希算法。SignHash()通过用私钥对该展开加密来计算指定哈希值的签。

       
(2).VerifyData():通过使用提供的公钥确定签名中的哈希值并将那个与所提供数据的哈希值进行比印证数字签名是否可行。

 public bool VerifyData(byte[] buffer, object halg, byte[] signature)
    {
      int calgHash = Utils.ObjToAlgId(halg, OidGroup.HashAlgorithm);
      return this.VerifyHash(Utils.ObjToHashAlgorithm(halg).ComputeHash(buffer), calgHash, signature);
    }

   
该法没有重载版本,有源码可以看看该方式接收三个参数,分别是:buffer已签字的数量,halg用于创造数量的哈希值的哈希算法名称,signature要验证的签字数据。该措施返回一个布尔型,如果签名中,则为
true;否则也
false。VerifyHash()通过采用提供的公钥确定签名中的哈希值并拿该与提供的哈希值进行较来证明数字签名是否可行。

   2.DSA类解析:

     (1).CreateSignature():创建指定数量的 Cryptography.DSA 签名。

 public abstract byte[] CreateSignature(byte[] rgbHash);

   
 该措施为一个虚幻方法,在派生类吃重写,接受一个字节数组表示若签的数据,返回指定数量的数字签名。在运CreateSignature方法时,必须协调创建SHA-1散列码,返回一个用字节数组表示的DSA签名。

     (2).VerifySignature():验证指定数量的 Cryptography.DSA 签名。

public abstract bool VerifySignature(byte[] rgbHash, byte[] rgbSignature);

     该法接受字符数组表示的SHA-1散列码和签署来说明。

    3.DSACryptoServiceProvider类解析:

     (1).ImportParameters():导入指定的
DSAParameters。该方法接受一个参数,Cryptography.DSA的参数。

   
 (2).VerifyData():通过以指定的签署数据与为指定数量计算的签进行较来说明指定的签约数据。

 public bool VerifyData(byte[] rgbData, byte[] rgbSignature)
    {
      return this.VerifyHash(this._sha1.ComputeHash(rgbData), (string) null, rgbSignature);
    }

     
该方法接受两个参数,rgbData已签字的数额;rgbSignature要证实的签约数据,如果签名验证为中,则为
true;否则,为
false。VerifyHash()通过将点名的署名数据和为指定哈希值计算的签署进行比来验证指定的签数据,我们看一下VerifyHash()的落实代码:

 public bool VerifyHash(byte[] rgbHash, string str, byte[] rgbSignature)
    {
      if (rgbHash == null)
        throw new ArgumentNullException("rgbHash");
      if (rgbSignature == null)
        throw new ArgumentNullException("rgbSignature");
      int calgHash = X509Utils.NameOrOidToAlgId(str, OidGroup.HashAlgorithm);
      if (rgbHash.Length != this._sha1.HashSize / 8)
      {
        string key = "Cryptography_InvalidHashSize";
        object[] objArray = new object[2];
        int index1 = 0;
        string str1 = "SHA1";
        objArray[index1] = (object) str1;
        int index2 = 1;
        // ISSUE: variable of a boxed type
        __Boxed<int> local = (ValueType) (this._sha1.HashSize / 8);
        objArray[index2] = (object) local;
        throw new CryptographicException(Environment.GetResourceString(key, objArray));
      }
      this.GetKeyPair();
      return Utils.VerifySign(this._safeKeyHandle, 8704, calgHash, rgbHash, rgbSignature);
    }

   
 该方法接收三独参数,rgbHash要签署的多寡的哈希值,str用于创造数量的哈希值的哈希算法名称,rgbSignature要证明的签数据。

    4.X509Certificate类解析:

       
该类在System.Security.Cryptography.X509Certificates空间下,提供协助您利用
X.509 v.3 证书的办法。

      (1).LoadCertificateFromBlob():加载证书:

private void LoadCertificateFromBlob(byte[] rawData, object password, X509KeyStorageFlags keyStorageFlags)
    {
      if (rawData == null || rawData.Length == 0)
        throw new ArgumentException(Environment.GetResourceString("Arg_EmptyOrNullArray"), "rawData");
      if (X509Utils.MapContentType(X509Utils._QueryCertBlobType(rawData)) == X509ContentType.Pfx && (keyStorageFlags & X509KeyStorageFlags.PersistKeySet) == X509KeyStorageFlags.PersistKeySet)
        new KeyContainerPermission(KeyContainerPermissionFlags.Create).Demand();
      uint dwFlags = X509Utils.MapKeyStorageFlags(keyStorageFlags);
      IntPtr num = IntPtr.Zero;
      RuntimeHelpers.PrepareConstrainedRegions();
      try
      {
        num = X509Utils.PasswordToHGlobalUni(password);
        X509Utils._LoadCertFromBlob(rawData, num, dwFlags, (keyStorageFlags & X509KeyStorageFlags.PersistKeySet) != X509KeyStorageFlags.DefaultKeySet, ref this.m_safeCertContext);
      }
      finally
      {
        if (num != IntPtr.Zero)
          Marshal.ZeroFreeGlobalAllocUnicode(num);
      }
    }

   该方法是X509Certificate类构造函数等几单道加载证书的现实性实现方式。

      (2).Export():使用指定的格式和密码将目前
X509Certificate对象导出到字节数组。

 public virtual byte[] Export(X509ContentType contentType, SecureString password)
    {
      return this.ExportHelper(contentType, (object) password);
    }

        该法接受两个参数,contentType描述如何设置输出数据格式的
X509ContentType 值之一。password访问 X.509
证书数据所用的密码。返回表示即 X509Certificate 对象的字节数组。

四.DotNet数字签名实例:

    下面提供一个X509Certificate的操作方法实例:

  public void EncryptXmlDocument(string arqXmlAssinar, string tagAssinatura, string tagAtributoId, X509Certificate2 x509Cert)
        {
            StreamReader sr = null;
            try
            {
                sr = System.IO.File.OpenText(arqXmlAssinar);
                var xmlString = sr.ReadToEnd();
                sr.Close();
                sr = null;
                XmlDocument doc = new XmlDocument { PreserveWhitespace = false };
                doc.LoadXml(xmlString);
                if (doc.GetElementsByTagName(tagAssinatura).Count == 0)
                {
                    throw new Exception(tagAssinatura.Trim());
                }
                if (doc.GetElementsByTagName(tagAtributoId).Count == 0)
                {
                    throw new Exception(tagAtributoId.Trim());
                }
                XmlNodeList lists = doc.GetElementsByTagName(tagAssinatura);
                foreach (XmlNode nodes in lists)
                {
                    foreach (XmlNode childNodes in nodes.ChildNodes)
                    {
                        if (!childNodes.Name.Equals(tagAtributoId))
                            continue;
                        if (childNodes.NextSibling != null && childNodes.NextSibling.Name.Equals("Signature"))
                            continue;
                        Reference reference = new Reference { Uri = "" };                                 
                        XmlElement childElemen = (XmlElement)childNodes;
                        if (childElemen.GetAttributeNode("Id") != null)
                        {
                            var attributeNode = childElemen.GetAttributeNode("Id");
                            if (attributeNode != null)
                                reference.Uri = "#" + attributeNode.Value;
                        }
                        else if (childElemen.GetAttributeNode("id") != null)
                        {
                            var attributeNode = childElemen.GetAttributeNode("id");
                            if (attributeNode != null)
                                reference.Uri = "#" + attributeNode.Value;
                        }
                        XmlDocument documentoNovo = new XmlDocument();
                        documentoNovo.LoadXml(nodes.OuterXml);
                        SignedXml signedXml = new SignedXml(documentoNovo) { SigningKey = x509Cert.PrivateKey };
                        XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
                        reference.AddTransform(env);
                        XmlDsigC14NTransform c14 = new XmlDsigC14NTransform();
                        reference.AddTransform(c14);
                        signedXml.AddReference(reference);
                        KeyInfo keyInfo = new KeyInfo();
                        keyInfo.AddClause(new KeyInfoX509Data(x509Cert));
                        signedXml.KeyInfo = keyInfo;
                        signedXml.ComputeSignature();
                        XmlElement xmlDigitalSignature = signedXml.GetXml();
nodes.AppendChild(doc.ImportNode(xmlDigitalSignature, true));
                    }
                }
                var xmlDoc = doc;
                var stringXmlAssinado = xmlDoc.OuterXml;
                StreamWriter sw2 = System.IO.File.CreateText(arqXmlAssinar);
                sw2.Write(stringXmlAssinado);
                sw2.Close();
            }
            catch (CryptographicException ex)
            {
                throw new CryptographicException(ex.Message);
            }
            catch (Exception e)
            {
                throw new Exception(e.Message);
            }
            finally
            {
                if (sr != null) sr.Close();
            }
        }

五.总结:

 
 上面是起关.NET数字证书的简便介绍,如产生描绘的非正常的地方还望多多原谅,在博文被来些类和艺术无比较多之罗列出,有趣味的得好去深入之摸底。我们学一个学问时,已经起文化之布局了解开始,这样方便我们站在全局思考问题。

 

加密算法系列:

     
 DotNet加密方法分析–散列加密:http://www.cnblogs.com/pengze0902/p/6268700.html

  个人档案   
 DotNet加密方法分析–对如加密:http://www.cnblogs.com/pengze0902/p/6268702.html

     
 DotNet加密方法分析–数字签名:http://www.cnblogs.com/pengze0902/p/6268709.html

     
 DotNet加密方法分析–非对如加密:http://www.cnblogs.com/pengze0902/p/6268705.html

超过平台的.NET邮件协议MailKit组件解析

   发起的.NET
Core开源组织号召,进展的快是自个儿自己为未尝想到的,很多园友都积极参与(虽然有些人诚心砸场子,要是坐自原先的宝脾气,这会该受我于住院了咔嚓,不过幸而是个别,做同项事究竟有人说好,也有人说是用武汉讲话说“闹眼子”),.NET社区不是尚未愿意共享文化之丁,只是没一个完完全全和出彩的生态环境,总之要国内的.NET发展更加强大。我在这边想到一句子话“我们盼望团结好做巨浪,但我们为乐于做巨浪来袭前的小浪”。

 
 上面拉了(我顿时人涉及正事前,都使将有些拉扯的语,这个习惯改不丢掉了…)

 
 项目遭到以及时的通信,有直接发多少及页面,也有以短信通知,也来我门今天介绍的邮件组件。我们今天的重中之重职责就是教一下发一个.NET底免费开源之邮件组件MailKit。本文将仍然的结合实例和零部件底层代码讲解一下有关组件的知。(项目招人的上,我都见面咨询一样下.NET的底层原理,有一个大神问我如此产生啊意义为?我们呢刻画不出.NET底层那样的良处理方式,为何取了解这些,其实自己个人觉得,问底的规律,只是为以与好的拍卖局部主次出现的问题,以及针对程序编码的时,选择最好适用的方提升性能,任何一样栽艺术还发生优势以及劣势,.NET的类库代码也是这样,如果我们知道.NET的根实现,我们于档次之需要实现时,可以根据.NET底层实现,选择适宜的不二法门,以告性能最美)。

一.Mailkit组件概述

 
 项目被采取Email的操作会比多,一般不怎么充分一点之类,都见面动用及邮件操作就一个操作。对于.NET邮件操作的零部件和艺术于多,今天我们即便介绍一缓邮件操作的零件MailKit,这个邮件组件是一个开源免费之,我们今天就算来了解一下随即一个零件的风味。MimeKit旨在通过尽可能接近地按MIME规范来缓解此问题,同时还也程序员提供了一个非常容易使用的高等级API。

 
 组件的支持的客户端类型比较多,例如SMTP客户端、POP3客户端、IMAP客户端。该器件是一个跨平台的Email组件,该零件支持.NET
4.0,.NET 4.5,Xamarin.Android,Xamarin.iOS,Windows Phone
8.1等等平台。该零件提供了一个MIME解析器,组件有的剖析特性灵活、性能大、很好之处理千头万绪的破损的MIME格式化。MimeKit的性实际上与GMime相当。

   该零件在安全性的尚是于强的,处理安全的计比较多,SASL认证、支持S /
MIME v3.2、支持OpenPGP、支持DKIM签名等等方式。Mailkit组件可以经CancellationToken取消相应之操作,CancellationToken传播应取消操作的打招呼,一个底CancellationToken使线程,线程池工作类里,或撤销合作任务之目标。过实例化CancellationTokenSource对象来创造取消令牌,该对象管理从该CancellationTokenSource.Token属性检索的取消令牌。然后,将收回令牌传递及应有吸纳取消通知之轻易数量的线程,任务或操作。令牌不克用来启动取消。

  MailKit组件支持异步操作,在里编写的关于I/O异步操作的好像。

二.MailKit实例:

   
上面介绍了MailKit组件的背景以及特色,这里就介绍一下Email组件底大概以。

  1.创造邮件方式:

 public void SentEmail(string path)
        {
            var message = new MimeMessage();
            //获取From标头中的地址列表,添加指定的地址
            message.From.Add(new MailboxAddress("Joey", "joey@friends.com"));
            //获取To头中的地址列表,添加指定的地址
            message.To.Add(new MailboxAddress("Alice", "alice@wonderland.com"));
            //获取或设置消息的主题
            message.Subject = "How you doin?";
            // 创建我们的消息文本,就像以前一样(除了不设置为message.Body)
            var body = new TextPart("plain")
            {
                Text = @"Hey Alice-- Joey"
            };
            // 为位于路径的文件创建图像附件
            var attachment = new MimePart("image", "gif")
            {
                ContentObject = new ContentObject(File.OpenRead(path), ContentEncoding.Default),
                ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
                ContentTransferEncoding = ContentEncoding.Base64,
                FileName = Path.GetFileName(path)
            };
            // 现在创建multipart / mixed容器来保存消息文本和图像附件
            var multipart = new Multipart("mixed")
            {
                body, attachment
            };
            // 现在将multipart / mixed设置为消息正文 
            message.Body = multipart;
        }

 
 调用该零件发送邮件与为邮件上加附件是比较简单的,第一步是实例化MimeMessage对象,对于该对象的解析将以底下进行,得到MimeMessage对象后,指定邮件的地方和主题等等相关信息。第二步实例化TextPart对象,为对象设定文本信息。若得咨询邮件创建文件的附件,可以使用MimePart对象,包含内容(如信息正文文本或)的叶节点MIME部分一个附件。第四步为开创的邮件主体和文件及附件信息后,可以创造Multipart对象,创建邮件容器,用来装文本信息及附件。最后调用MimeMessage.body属性获取或设置信息的正文。

    2.邮件信息之辨析:

var message = MimeMessage.Load(stream);

 
 邮件的音信我们需要进行相应之辨析,这里我们运用MimeMessage的Load方法,该方式从指定的流加载MimeKit.MimeMessage。另一个加载数据的方法,可以运用MimeParser类,这里就不再解析了。

    3.邮件的收纳:

   public static void HandleMimeEntity(MimeEntity entity)
        {
            //MimeEntity转化为Multipart实体
            var multipart = entity as Multipart;
            if (multipart != null)
            {
                for (int i = 0; i < multipart.Count; i++)
                    HandleMimeEntity(multipart[i]);
                return;
            }
            var rfc822 = entity as MessagePart;

            if (rfc822 != null)
            {
                var message = rfc822.Message;
                HandleMimeEntity(message.Body);
                return;
            }
            var part = (MimePart)entity;
        }

 
 以上是指向吸纳及之音之一个遍历,采用递归遍历MIME结构。MIME是内容之培育结构,很像一个文件系统。MIME确实定义了同样组通用规则,用于邮件客户端如何解释MIME部分的扶植结构。的 内容处置头是为了为接受客户端提供提醒为安有凡以显得作为消息体的如出一辙部分,并且完全在受说啊附件。另外两种艺术这距就无做牵线了。

三.MailKit核心对象解析

 
 上面介绍了Email的基本操作就未举行了多之介绍,在运用该器件时,较为的粗略。这里虽来探望该器件的色结构以及局部着力目标。类库结构发生如下图:

澳门新葡亰官网 1

    1.MimeMessage.Load():

public static MimeMessage Load (ParserOptions options, Stream stream, bool persistent, 
                                CancellationToken cancellationToken = default (CancellationToken))
        {
            if (options == null)
                throw new ArgumentNullException (nameof (options));

            if (stream == null)
                throw new ArgumentNullException (nameof (stream));

            var parser = new MimeParser (options, stream, MimeFormat.Entity, persistent);

            return parser.ParseMessage (cancellationToken);
        }

   
 该法从指定的流加载MimeMessage,具有6单艺术重载。该方式返回一个MimeMessage对象,有源码可以看,在该方法中创立了一个MimeParser对象,MimeParser包含内容(例如邮件正文文本或附件)的叶节点MIME部分。调用ParseMessage方法,解析来自流的音信。

   2.TextPart.Text:

public string Text {
            get {
                if (ContentObject == null)
                    return string.Empty;
                var charset = ContentType.Parameters["charset"];
                using (var memory = new MemoryStream ()) {
                    ContentObject.DecodeTo (memory);
                    var content = memory.ToArray ();
                    Encoding encoding = null;
                    if (charset != null) {
                        try {
                            encoding = CharsetUtils.GetEncoding (charset);
                        } catch (NotSupportedException) {
                        }
                    }
                    if (encoding == null) {
                        try {
                            return CharsetUtils.UTF8.GetString (content, 0, (int) memory.Length);
                        } catch (DecoderFallbackException) {
                            encoding = CharsetUtils.Latin1;
                        }
                    }
                    return encoding.GetString (content, 0, (int) memory.Length);
                }
            }
            set {
                SetText (Encoding.UTF8, value);
            }
        }

   
该属性获取解码的文件内容。该属性是一个可读而写的性能澳门新葡亰官网。ContentType.Parameters[“charset”]用来获取charset参数的值。该方法用来将参数的价值设置为数量流并设置相应的编码。看到此的十分处理组织,就想大概的谈话几句,.NET的不胜比较的懦弱,很多时段在写.NET的百般时就逾的简练,以上是针对性大知识捕获,有些地方并没有做处理,有些地方是针对异常的地方开展还原。

   3.MimeEntity.WriteTo():

public virtual void WriteTo (FormatOptions options, Stream stream, bool contentOnly, 
                            CancellationToken cancellationToken = default (CancellationToken))
        {
            if (options == null)
                throw new ArgumentNullException (nameof (options));

            if (stream == null)
                throw new ArgumentNullException (nameof (stream));

            if (!contentOnly)
                Headers.WriteTo (options, stream, cancellationToken);
        }

   
该办法以MimeEntity写副到指定的多少流中,该措施接受参数options格式选项。stream输出数据流,contentOnly判断是否可写。该方式定义为虚方法,在此起彼伏这个方后,可以于子类种对该法进行重写。

四.总结

 
 本人看以列开发中,如果引入了第三着组件,我们尽量引入组件的源码,这样咱们本着全组件的组织有一个认识,组件的落实方式我们吧足以进行周密了解,尤其是我们于展开调剂之下越来越有因此,我们可逐一的拓断点调试。以上是指向该器件的一个简单介绍,有趣味之好错过深入的询问及上学。

个人档案DotNet友元程序集解析

 
 项目开之过程遭到,调试使用的或是极度多的操作。任何代码写出来都得经调试和整合,以此扩展和提升程序的安澜与可靠性。谈到.NET的单元测试,在此虽得提提.NET的友元程序集就同特征,也借用.NET进行单元测试的一个较为好用的.NET属性,来教一下程序集、定制Attribute的有关知识。一些知识要频繁的失品尝和自省,不要以为您会了就算无检点,等公放在心上的时候,你就出若干力不从心的意思了。

   生活在不歇的煎熬,只有由此磨练,才不过掌握何时要安分,何时要挑战。

   毒鸡汤喝了了,来聊聊正事…

一.程序集概述

 
本文主要是讨论“友元程序集”的组成部分文化,既然是召开一个分析,那么即使应有把有学问做一个拓展来阐释。在此间先谈谈程序集(有人以为异常了解,有人觉得了无知道,情况不同,选择不同,需者自取吧),接下去我们具体的省程序集就无异于表征。

 
程序集是一个或多只模块/资源文件之逻辑分组,程序集是录取、安全性以及版本控制的极端小单元。对于程序集的结构发生如下图。

个人档案 1

个人档案 2

   
对于程序集的组成就不一一做分析,在这边就独自谈谈元数据就同一结构。元数据是一个二进制数据块,由同样组数据表,元数据连接与分包IL代码的文本涉及,元数据由几个说明组成。元数据的作用有高达图介绍。元数据的表有三独品种:定义表,引用表,清单表。对于这些发明底布局于此处就不开牵线了,有趣味之好查找一下,个人认为第一数据及时同样布局应当可以的研讨一下。

二.定制Attribute概述

 
 上面的阐发中简单的介绍了程序集的构造与首家数据,在此地大概的介绍一下定制Attribute这一.NET的特征。定制Attribute允许定义的信用叫几每一个第一数据表记录项,这种可扩大的初数据信息可知以运行时查询,从而动态改变代码的履行方。在C#种,为了将一个定制Attribute应用为一个对象元素,需要以Attribute放置于目标元素前面的同一对准方括号中。

 
 CLR允许以定制Attribute应用被可当文书之头版数据中代表的几乎所有因素。定制Attribute主要用被程序集、模块、类型、字段、方法、方法参数、方法返回值、属性、事件、泛型类型参数。attribute是看似的一个实例,将一个attribute应用叫一个目标元素时,语法类似于调用类的某某实例构造函数。定制Attribute的实例如下:

[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]

   
上面代码取自InternalsVisibleToAttribute类中,该类是到位友元程序集特性的着力目标,下面会开一个有血有肉的牵线。AttributeUsage需要经受一个AttributeTargets枚举类型的价值作为参数,称之为定位参数,是强制性的,必须指定。AllowMultiple参数用于获取或设置一个布尔值,指示是否来差不多单实例指定的性能可以吗单个程序元素指定。Inherited参数用于获取或安装一个布尔值,指示指定的习性是否可延续由派生类和重写成员。

 
 定制Attribute可以使被单个目标元素,定制Attribute的各个是不值一提的。在C#种,可将每个Attribute都封闭到同一对方括号丁,也得以有方括号受查封多单因逗号分隔的Attribute。

 
 定制Attribute就是一个像样的实例,它被序列化成驻留于首届数据被之一个字节流,在运行时,可以对元数据遭到含有的字节进行反序列化,从而构筑造类的一个实例。

三.友元程序集解析

 
 扯了一半天,终于到教学“友元程序集”这一个定义,“友元程序集”在.NET2着提出,使用InternalsVisibleToAttribute来贯彻即时同一特性,InternalsVisibleTo只能用来程序集,并且你可以于同一个先后集种应用多次。源程序集:包含这个特性之先后集。友元程序集能够见到源程序集的富有中成员,类似于国有的。

   友元程序集实例介绍:

//AssemblySource.DLL
[assembly: InternalsVisibleTo(DotNetShare)]
public class AssemblySource
{
    public static void Share();
}


//DotNetShare.DLL
public class DotNetShare
{
    private static void Main()
    {
        AssemblySource.Share();
    }
}

   
AssemblySource.DLL和DotNetShare.DLL之间有同样种植新鲜之关联,但是这种关系只能单项操作。接下来看一下InternalsVisibleToAttribute的兑现源码。InternalsVisibleToAttribute继承自Attribute类,该类指定通常就于时次集中可见的类型对点名程序集可见。该类包含两单特性和一个办法。

   1.AssemblyName

public string AssemblyName
    {
      [__DynamicallyInvokable] get
      {
        return this._assemblyName;
      }
    }

 
 该属性也一个但读属性,一个象征友元程序集名称的字符串。该属性用于获取友元程序集的名称,采用
internal 关键字记的装有品类以及种成员对该程序集均为可见。

  2.InternalsVisibleToAttribute()

public InternalsVisibleToAttribute(string assemblyName)
    {
      this._assemblyName = assemblyName;
    }

   该方式吗一个构造函数,用指定的友元程序集的名初始化 <see
cref=”T:System.Runtime.CompilerServices.InternalsVisibleToAttribute”/>
类的初实例。接收一个友元程序集的名目。

 
对于友元程序集有一个约,如果一个友元程序个人档案集是签约的,那么源程序集为了保险信任是的代码,就待指定友元程序集的公钥。

四.总结

 
 对于本文主要是当介绍友元程序集就等同特色,顺带介绍程序集和定制Attribute这片单特点,方便大家领略友元程序集就同特征。这首文章要对大家有助,还是那么句话,需者自取,也虚心接受吐槽。知识在分享,更在于各级一个丁的合计。

 

个人档案越平台的.NET邮件协议MailKit组件解析

   发起的.NET
Core开源组织号召,进展的进度是自个儿自己也没想到的,很多园友都积极参与(虽然小人诚心诚意砸场子,要是坐自己原先的宝脾气,这会当叫我由住院了咔嚓,不过幸而是少数,做一样项事究竟有人说好,也有人说是用武汉语说“闹眼子”),.NET社区不是从来不愿意共享知识之总人口,只是没有一个一体化与不错的生态环境,总之要国内的.NET发展尤其强。我当这里想到一词话“我们期待自己可开巨浪,但我们也心甘情愿做巨浪来袭前的小浪”。

 
 上面拉了(我及时丁提到正事前,都如以部分闲话的语,这个习惯变更不丢了…)

 
 项目面临为了这的通信,有直接发数到页面,也生动短信通知,也生我门今天介绍的邮件组件。我们今天的重要任务就是是教一下闹一个.NET之免费开源之邮件组件MailKit。本文将依然的结实例和组件底层代码讲解一下连锁组件的学问。(项目招人的下,我还见面咨询一样下.NET的底色原理,有一个大神问我这么来啊含义也?我们吧描绘不出.NET底层那样的优处理方式,为何取了解这些,其实自己个人认为,问底的规律,只是向为和好的拍卖部分先后出现的问题,以及针对性先后编码的下,选择最适于的主意提升性,任何一样栽方法还发优势与劣势,.NET的类库代码也是如此,如果我们知道.NET的底部实现,我们在类型的需要实现时,可以根据.NET底层实现,选择当的措施,以告性能最好精)。

一.Mailkit组件概述

 
 项目中利用Email的操作时比多,一般不怎么好一点之种类,都见面以及邮件操作就一个操作。对于.NET邮件操作的机件和措施比较多,今天咱们就算介绍一舒缓邮件操作的组件MailKit,这个邮件组件是一个开源免费的,我们现在即令来打探一下当即一个零部件的特点。MimeKit旨在通过尽可能接近地照MIME规范来化解此问题,同时还呢程序员提供了一个非常容易使用的高等API。

 
 组件的支撑的客户端类型比较多,例如SMTP客户端、POP3客户端、IMAP客户端。该零件是一个跨平台的Email组件,该器件支持.NET
4.0,.NET 4.5,Xamarin.Android,Xamarin.iOS,Windows Phone
8.1之类平台。该器件提供了一个MIME解析器,组件有的解析特性灵活、性能大、很好的拍卖千头万绪的烂之MIME格式化。MimeKit的属性实际上和GMime相当。

   该器件在安全性的还是比大之,处理平安之章程较多,SASL认证、支持S /
MIME v3.2、支持OpenPGP、支持DKIM签名等等方式。Mailkit组件可以透过CancellationToken取消相应的操作,CancellationToken传播应撤销操作的通,一个之CancellationToken使线程,线程池工作类里面,或取消合作任务的对象。过实例化CancellationTokenSource对象来创造取消令牌,该目标管理由夫CancellationTokenSource.Token属性检索的吊销令牌。然后,将撤销令牌传递到当收取消通知的肆意数量之线程,任务要操作。令牌不能够用于启动取消。

  MailKit组件支持异步操作,在里面编写的关于I/O异步操作的类似。

二.MailKit实例:

   
上面介绍了MailKit组件的背景以及特色,这里就是介绍一下Email组件的简单用。

  1.创建邮件方式:

 public void SentEmail(string path)
        {
            var message = new MimeMessage();
            //获取From标头中的地址列表,添加指定的地址
            message.From.Add(new MailboxAddress("Joey", "joey@friends.com"));
            //获取To头中的地址列表,添加指定的地址
            message.To.Add(new MailboxAddress("Alice", "alice@wonderland.com"));
            //获取或设置消息的主题
            message.Subject = "How you doin?";
            // 创建我们的消息文本,就像以前一样(除了不设置为message.Body)
            var body = new TextPart("plain")
            {
                Text = @"Hey Alice-- Joey"
            };
            // 为位于路径的文件创建图像附件
            var attachment = new MimePart("image", "gif")
            {
                ContentObject = new ContentObject(File.OpenRead(path), ContentEncoding.Default),
                ContentDisposition = new ContentDisposition(ContentDisposition.Attachment),
                ContentTransferEncoding = ContentEncoding.Base64,
                FileName = Path.GetFileName(path)
            };
            // 现在创建multipart / mixed容器来保存消息文本和图像附件
            var multipart = new Multipart("mixed")
            {
                body, attachment
            };
            // 现在将multipart / mixed设置为消息正文 
            message.Body = multipart;
        }

 
 调用该零件发送邮件及为邮件上加附件是比较简单的,第一步是实例化MimeMessage对象,对于该对象的解析将以脚进行,得到MimeMessage对象后,指定邮件的地点与主题等等相关信息。第二步实例化TextPart对象,为对象设定文本信息。若得咨询邮件创建文件的附件,可以运用MimePart对象,包含内容(如信息正文文本或)的叶节点MIME部分一个附件。第四步为创建的邮件主体及文书以及附件信息后,可以创造Multipart对象,创建邮件容器,用来装文本信息及附件。最后调用MimeMessage.body属性获取或设置信息之正文。

    2.邮件信息之辨析:

var message = MimeMessage.Load(stream);

 
 邮件的信息我们得开展相应的分析,这里我们利用MimeMessage的Load方法,该措施从指定的流加载MimeKit.MimeMessage。另一个加载数据的计,可以用MimeParser类,这里虽不再解析了。

    3.邮件的吸收:

   public static void HandleMimeEntity(MimeEntity entity)
        {
            //MimeEntity转化为Multipart实体
            var multipart = entity as Multipart;
            if (multipart != null)
            {
                for (int i = 0; i < multipart.Count; i++)
                    HandleMimeEntity(multipart[i]);
                return;
            }
            var rfc822 = entity as MessagePart;

            if (rfc822 != null)
            {
                var message = rfc822.Message;
                HandleMimeEntity(message.Body);
                return;
            }
            var part = (MimePart)entity;
        }

 
 以上是对吸收及的消息之一个遍历,采用递归遍历MIME结构。MIME是内容的树结构,很像一个文件系统。MIME确实定义了同等组通用规则,用于邮件客户端如何诠释MIME部分的培育结构。的 内容处置头是为着让接客户端提供提醒为什么有凡为显得作为消息体的均等组成部分,并且完全在受解说吗附件。另外两种植方法及时去就非开牵线了。

三.MailKit核心对象解析

 
 上面介绍了Email的基本操作就非做过多的介绍,在利用该零件时,较为的概括。这里虽来探视该器件的种结构与有些核心目标。类库结构有如下图:

个人档案 1

    1.MimeMessage.Load():

public static MimeMessage Load (ParserOptions options, Stream stream, bool persistent, 
                                CancellationToken cancellationToken = default (CancellationToken))
        {
            if (options == null)
                throw new ArgumentNullException (nameof (options));

            if (stream == null)
                throw new ArgumentNullException (nameof (stream));

            var parser = new MimeParser (options, stream, MimeFormat.Entity, persistent);

            return parser.ParseMessage (cancellationToken);
        }

   
 该办法从指定的流加载MimeMessage,具有6个措施重载。该法返回一个MimeMessage对象,有源码可以看看,在拖欠方式中创立了一个MimeParser对象,MimeParser包含内容(例如邮件正文文本或附件)的叶节点MIME部分。调用ParseMessage方法,解析来自流的消息。

   2.TextPart.Text:

public string Text {
            get {
                if (ContentObject == null)
                    return string.Empty;
                var charset = ContentType.Parameters["charset"];
                using (var memory = new MemoryStream ()) {
                    ContentObject.DecodeTo (memory);
                    var content = memory.ToArray ();
                    Encoding encoding = null;
                    if (charset != null) {
                        try {
                            encoding = CharsetUtils.GetEncoding (charset);
                        } catch (NotSupportedException) {
                        }
                    }
                    if (encoding == null) {
                        try {
                            return CharsetUtils.UTF8.GetString (content, 0, (int) memory.Length);
                        } catch (DecoderFallbackException) {
                            encoding = CharsetUtils.Latin1;
                        }
                    }
                    return encoding.GetString (content, 0, (int) memory.Length);
                }
            }
            set {
                SetText (Encoding.UTF8, value);
            }
        }

   
该属性获取解码的文本内容。该属性是一个但读而写的性质个人档案。ContentType.Parameters[“charset”]用来获取charset参数的价。该方法用来用参数的值设置为多少流并设置相应的编码。看到此间的那个处理组织,就想大概的讲话几句子,.NET的不行比较的软弱,很多时刻在写.NET的可怜时虽进一步的略,以上是针对那个知识捕获,有些地方并从未召开处理,有些地方是对老的地方进行回复。

   3.MimeEntity.WriteTo():

public virtual void WriteTo (FormatOptions options, Stream stream, bool contentOnly, 
                            CancellationToken cancellationToken = default (CancellationToken))
        {
            if (options == null)
                throw new ArgumentNullException (nameof (options));

            if (stream == null)
                throw new ArgumentNullException (nameof (stream));

            if (!contentOnly)
                Headers.WriteTo (options, stream, cancellationToken);
        }

   
该措施将MimeEntity写副到指定的数码流中,该方法接受参数options格式选项。stream输出数据流,contentOnly判断是否可写。该法定义也虚方法,在继承这个措施后,可以当子类种对拖欠方式开展重写。

四.总结

 
 本人认为当路开被,如果引入了第三正值组件,我们尽量引入组件的源码,这样我们对合组件的布局来一个认识,组件的实现方式我们也得开展细致了解,尤其是咱当进展调试之之后更进一步有因此,我们得逐一的进行断点调试。以上是对准拖欠零件的一个简短介绍,有趣味之可以错过深入之询问和习。

【转】再称IQueryable,揭开表达式树的心腹面纱

【转】再张嘴IQueryable<T>,揭开表达式树的秘面纱

接上篇《预先说IEnumerable,我们每天用之foreach你实在了解她吗?》

前不久园里定制好的orm那是一个风生水从,感觉不整自己之orm都非好意思继续混博客园了(开单玩笑)。那么在此之前我们发必不可少仔细了解下 IQueryable<T> ,于是就发生了此文。

哎是培育?

咦是培养?这个题材好像有些白痴。树不纵是树嘛。看图:

个人档案 1

咱打极度下的中坚开始为上看,主枝-分支-分支….可以说凡是无比分支下去。我们倒过来看就是如此:

个人档案 2

平时我们因此得极其多之扶植结构数据就是是XML了,节点下面可以尽上加子节点。我们想平时尚用过啊树结构数据,比如:菜单无限分级、评论区的楼宇。

眼看和我们今天云的发生毛关系啊。… 我们今天要就是是来分析表达式树的。、

lambda表达式和表达式树的区分:

Lambda表达式:

Func<Student, bool> func = t => t.Name == "农码一生";

表达式树: 

Expression<Func<Student, bool>> expression = t => t.Name == "农码一生"; 

 咋一看,没啥区别啊。表达式只是用Expression包了一下耳。那尔擦了,这单是Microsoft给咱展示的障眼法,我们看编译后的C#代码:

个人档案 3

率先只lambda表达式编译成了匿名函数,第二单表达式树编译成一了同积我们不认的事物,远较咱本描绘的lambda复杂得差不多。

结论:

  • 俺们平素采用的表达式树,是编辑的lambda表达式然后编译成的表达式树,也就是说平时相像情形用的表达式树都是编译器帮我们完成的。(当然,我们可得手动的积极的失去创表达式树。只是太累,不是必备情况并未孰愿意去干这个苦活呢)

我们来探视表达式树到底出什么神奇之地方:

个人档案 4

发出没产生看起点感觉来?Body里面来Right、Left,Right里面又发生Right、Left,它们的路且是后续自 Expression 。这种节点下面有节点,可以无限叠加下去的数据结构我们叫树结构数据。也就算是咱的抒发式树。

补:上面的 Student 实体类:

个人档案 5个人档案 6

public class Student
{
    public string Name { get; set; }

    public int Age { get; set; }

    public string Address { get; set; }

    public string Sex { get; set; }
}

View Code

分析表达式树

地方我们看看了所谓的表达式树,其他也绝非设想的那复杂嘛。不就是一个培养结构数据嘛。如果我们而兑现协调的orm,免不了而分析表达式树。一般说到剖析树结构数据都见面就此到递归算法。下面我们初步解析表达式树。

先行定义解析方法:

//表达式解析
public static class AnalysisExpression
{
    public static void VisitExpression(Expression expression)
    {
        switch (expression.NodeType)
        {
            case ExpressionType.Call://执行方法
                MethodCallExpression method = expression as MethodCallExpression;
                Console.WriteLine("方法名:" + method.Method.Name);
                for (int i = 0; i < method.Arguments.Count; i++)
                    VisitExpression(method.Arguments[i]);
                break;
            case ExpressionType.Lambda://lambda表达式
                LambdaExpression lambda = expression as LambdaExpression;
                VisitExpression(lambda.Body);
                break;
            case ExpressionType.Equal://相等比较
            case ExpressionType.AndAlso://and条件运算
                BinaryExpression binary = expression as BinaryExpression;
                Console.WriteLine("运算符:" + expression.NodeType.ToString());
                VisitExpression(binary.Left);
                VisitExpression(binary.Right);
                break;
            case ExpressionType.Constant://常量值
                ConstantExpression constant = expression as ConstantExpression;
                Console.WriteLine("常量值:" + constant.Value.ToString());
                break;
            case ExpressionType.MemberAccess:
                MemberExpression Member = expression as MemberExpression;
                Console.WriteLine("字段名称:{0},类型:{1}", Member.Member.Name, Member.Type.ToString());
                break;
            default:
                Console.Write("UnKnow");
                break;
        }
    }

}

调用解析方法:

Expression<Func<Student, bool>> expression = t => t.Name == "农码一生" && t.Sex == "男";
AnalysisExpression.VisitExpression(expression);

俺们来看看执行进程:

个人档案 7

如出一辙重叠一重叠的往子节点递归,直到遍历完所有的节点。最后打印效果如下:

个人档案 8

差不多我们怀念使的元素和价值都得到到了,接着怎么组装就扣留你协调的心情了。是拼成sql,还是生成url,请随意!

实现和谐之IQueryable<T>、IQueryProvider

单解析了表达式树就可鼓捣自己之orm了?不行,起码也要是根据 IQueryable<T> 接口来编码吧。

就我们打定义个类 MyQueryable<T> 继承接口 IQueryable<T> :

 public class MyQueryable<T> : IQueryable<T>
 {
     public IEnumerator<T> GetEnumerator()
     {
         throw new NotImplementedException();
     }
     IEnumerator IEnumerable.GetEnumerator()
     {
         throw new NotImplementedException();
     }
     public Type ElementType
     {
         get { throw new NotImplementedException(); }
     }
     public Expression Expression
     {
         get { throw new NotImplementedException(); }
     }
     public IQueryProvider Provider
     {
         get { throw new NotImplementedException(); }
     }
 }

咱见到里面起个接口属性 IQueryProvider ,这个接口的作用非常正在为,主要作用是于实践查询操作符的时候更创设 IQueryable<T> 并且最终遍历的时节实施sql远程取值。我们还看见了 Expression  属性。

如今我们明白了 IQueryable<T> 和 Expression (表达式树)的关联了咔嚓:

  •  IQueryable<T> 最根本的意就是用来存储 Expression(表达式树)

下我们呢从定义现实了 IQueryProvider 接口的类 MyQueryProvider :

public class MyQueryProvider : IQueryProvider
{
    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        throw new NotImplementedException();
    }
    public IQueryable CreateQuery(Expression expression)
    {
        throw new NotImplementedException();
    }
    public TResult Execute<TResult>(Expression expression)
    {
        throw new NotImplementedException();
    }
    public object Execute(Expression expression)
    {
        throw new NotImplementedException();
    }
}

方都是自动生成的伪代码,下面我们来填充具体的实现:

个人档案 9个人档案 10

    public class MyQueryProvider : IQueryProvider
    {
        public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
        {
            return new MyQueryable<TElement>(expression);
        }

        public IQueryable CreateQuery(Expression expression)
        {
            throw new NotImplementedException();
        }

        public TResult Execute<TResult>(Expression expression)
        {
            return default(TResult);
        }

        public object Execute(Expression expression)
        {
            return new List<object>();
        } 
    }  
    public class MyQueryable<T> : IQueryable<T>
    {
        public MyQueryable()
        {
            _provider = new MyQueryProvider();
            _expression = Expression.Constant(this);
        }

        public MyQueryable(Expression expression)
        {
            _provider = new MyQueryProvider();
            _expression = expression;
        }
        public Type ElementType
        {
            get { return typeof(T); }
        }

        private Expression _expression;
        public Expression Expression
        {
            get { return _expression; }
        }

        private IQueryProvider _provider;
        public IQueryProvider Provider
        {
            get { return _provider; }
        }

        public IEnumerator GetEnumerator()
        {
            return (Provider.Execute(Expression) as IEnumerable).GetEnumerator();
        }

        IEnumerator<T> IEnumerable<T>.GetEnumerator()
        {
            var result = _provider.Execute<List<T>>(_expression);
            if (result == null)
                yield break;
            foreach (var item in result)
            {
                yield return item;
            }
        }
    }

View Code

实施代码:

 var aa = new MyQueryable<Student>();
 var bb = aa.Where(t => t.Name == "农码一生");
 var cc = bb.Where(t => t.Sex == "男");
 var dd = cc.AsEnumerable();
 var ee = cc.ToList(); 

就我们看执行进程:

个人档案 11

结论:

  • 老是在实行 Where 查询操作符的时段 IQueryProvider 会为咱创建一个新的 IQueryable<T> 
  • 调用 AsEnumerable() 方法的时刻并无会见失掉实际取值(只是获得一个IEnumerable)[专注:在EF里面查询不要先取IEnumerable后滤筛,因为AsEnumerable()会转变查询全表的sql]
  • 尽 ToList() 方法时才去真正调用迭代器 GetEnumerator() 取值
  • 诚取值的时刻,会去实施 IQueryProvider 中之 Execute 方法。(就是在调用这个措施的当儿解析表达式数,然后实施得结果)

咱们来看确实当办实事的 Execute
 我们也叫他回回默认值了。

个人档案 12

今昔估算有人不爽了,你顶是切实落实下 Execute 。好吧!(其实通过地方说的剖析表达式树,你可协调以此做纪念做的外事了。)

第一为简单起见,我们因而一个集做啊数据源:

//构造Student数组
public static List<Student> StudentArrary = new List<Student>()
{
        new Student(){Name="农码一生", Age=26, Sex="男", Address="长沙"},
        new Student(){Name="小明", Age=23, Sex="男", Address="岳阳"},
        new Student(){Name="嗨-妹子", Age=25, Sex="女", Address="四川"}
};

然后,重新勾一个VisitExpression2智:(和前的区别:
现在目的是获取表达式树被的表达式,而不是再度组建成sql或别的)

public static void VisitExpression2(Expression expression, ref List<LambdaExpression> lambdaOut)
{
    if (lambdaOut == null)
        lambdaOut = new List<LambdaExpression>();
    switch (expression.NodeType)
    {
        case ExpressionType.Call://执行方法
            MethodCallExpression method = expression as MethodCallExpression;
            Console.WriteLine("方法名:" + method.Method.Name);
            for (int i = 0; i < method.Arguments.Count; i++)
                VisitExpression2(method.Arguments[i], ref  lambdaOut);
            break;
        case ExpressionType.Lambda://lambda表达式
            LambdaExpression lambda = expression as LambdaExpression;
            lambdaOut.Add(lambda);
            VisitExpression2(lambda.Body, ref  lambdaOut);
            break;
        case ExpressionType.Equal://相等比较
        case ExpressionType.AndAlso://and条件运算
            BinaryExpression binary = expression as BinaryExpression;
            Console.WriteLine("运算符:" + expression.NodeType.ToString());
            VisitExpression2(binary.Left, ref  lambdaOut);
            VisitExpression2(binary.Right, ref  lambdaOut);
            break;
        case ExpressionType.Constant://常量值
            ConstantExpression constant = expression as ConstantExpression;
            Console.WriteLine("常量值:" + constant.Value.ToString());
            break;
        case ExpressionType.MemberAccess:
            MemberExpression Member = expression as MemberExpression;
            Console.WriteLine("字段名称:{0},类型:{1}", Member.Member.Name, Member.Type.ToString());
            break;
        case ExpressionType.Quote:
            UnaryExpression Unary = expression as UnaryExpression;
            VisitExpression2(Unary.Operand, ref  lambdaOut);
            break;
        default:
            Console.Write("UnKnow");
            break;
    }
}

接下来再次实现方式 Execute :

public TResult Execute<TResult>(Expression expression)
{
    List<LambdaExpression> lambda = null;
    AnalysisExpression.VisitExpression2(expression, ref lambda);//解析取得表达式数中的表达式
    IEnumerable<Student> enumerable = null;
    for (int i = 0; i < lambda.Count; i++)
    {
        //把LambdaExpression转成Expression<Func<Student, bool>>类型
        //通过方法Compile()转成委托方法
        Func<Student, bool> func = (lambda[i] as Expression<Func<Student, bool>>).Compile(); 
        if (enumerable == null)
            enumerable = Program.StudentArrary.Where(func);//取得IEnumerable
        else
            enumerable = enumerable.Where(func);
    }
    dynamic obj = enumerable.ToList();//(注意:这个方法的整个处理过程,你可以换成解析sql执行数据库查询,或者生成url然后请求获取数据。)
    return (TResult)obj;
}

履进程:

个人档案 13

私对 IQueryable 延迟加载的接头:

  • 前段部分的查询操作符只是将逻辑讲存入表达式树,并从未远程执行sql。
  • foreache执行的凡 IEnumerable<T> ,然而 IEnumerable<T> 同样具有缓加载的特性。每次迭代的时节才真的的得到多少。且以运用导航属性的时刻会再查询数据库。(下次说延迟加载不要忘记了 IEnumerable 的功德哦!)

小知识:

表达式树转成为Lambda个人档案表达式:

Expression<Func<Student, bool>> expression = t => t.Name == "农码一生";
Func<Student, bool> func = expression.Compile();

总结:

发表式树的分析就是停下了,其中还有很多细节要要的尚未分析到。下次产生新的体验再来总结。

感表达式树就是先行把表达式打散存在树结构里(一般打散的历程是编译器完成),然后可以因不同之数据源或接口重新组建成自己想使之别款式,这吗为我们实现和谐之orm成为了或者。

今天重中之重是针对性发挥式树的辨析、和促成和谐的IQueryable<T>、IQueryProvider做了一个笔录以及小结,其中不定有误的下结论或说法,轻点拍!

demo下载:http://pan.baidu.com/s/1nvAksgL 

本文为合至索引目录:《C#基础知识巩固》

 

推荐阅读:

http://www.cnblogs.com/jesse2013/p/expressiontree-part1.html

http://www.cnblogs.com/jesse2013/p/expressiontree-part2.html

http://www.cnblogs.com/jesse2013/p/expressiontree-Linq-to-cnblogs.html

园友@风口上之猪推荐:

http://www.cnblogs.com/Ninputer/archive/2009/09/08/expression_tree3.html
http://blog.zhaojie.me/2009/03/expression-cache-1.html

 

CLR 协变、逆变

关押开看得多少晕了,协变、逆变傻傻分不清楚。

见状泛型和信托、委托方,发现就之中的协变和逆变不均等。

泛型的逆变和协变:
倘某返回路可以由该基类替换,那么是路就是支持协变的。
一经某参数类型可以由其派生类替换,那么这个路就是支撑逆变的。

 参考:http://www.cnblogs.com/IPrograming/p/4471130.html

以身作则代码如下:

public class BaseClass
{
    //...
}

public class DerivedClass : BaseClass
{
    //...
}

IEnumerable<DerivedClass> d = new List<DerivedClass>();
IEnumerable<BaseClass> b = d;

//IEnumerable<T> 接口的定义(支持协变)
public interface IEnumerable<out T> : IEnumerable

 

委托方的逆变和协变:

设若有返回路可以由其派生类替换,那么这个类型就是支撑协变的。
假定有参数类型可以由其基类替换,那么这类型就是永葆逆变的。

 

    public delegate object TestDelegate(string str);

    class MyClass
    {
        public string t1(string s)
        {
            return "";
        }

        public object t2(object s)
        {
            return "";
        }

        public string t3(object s)
        {
            return "";
        }

        public int t4(string s)//值类型不可以,只有引用类型支持协变和逆变
        {
            return 0;
        }
        public void main()
        {
            TestDelegate td1 = t1;
            TestDelegate td2 = t2;
            TestDelegate td3 = t3;
            TestDelegate td4 = t4;//编译报错,返回类型错误
        }
    }

 

 

 

 

自身今天啊生接触痴。

民用看开跟查的素材所得,如有错误,欢迎指正。

深切探索.NET框架中了解CLR如何创造运行时对象

原文地址:http://msdn.microsoft.com/en-us/magazine/cc163791.aspx
初稿发布日期: 9/19/2005
原文已经深受 Microsoft
删除了,收集过程被发觉许多稿子图都非净,那是因原文的图都未统,所以特收集完整全文。

目录

  • 前言
  • CLR启动程序(Bootstrap)创建的地域
  • 系统域(System
    Domain)
  • 共享域(Shared
    Domain)
  • 默认域(Default
    Domain)
  • 加载器堆(Loader
    Heaps)
  • 色原理
  • 目标实例
  • 方法表
  • 基实例大小
  • 术槽表(Method Slot
    Table)
  • 术描述(MethodDesc)
  • 接口虚表图和接口图(Interface Vtable Map and Interface
    Map)
  • 虚分派(Virtual
    Dispatch)
  • 静态变量(Static
    Variables)
  • EEClass
  • 结论

前言

  • SystemDomain, SharedDomain, and DefaultDomain。
  • 靶布局以及舅存细节。
  • 艺术发明布局。
  • 方式分派(Method dispatching)。

盖国有语言运行时(CLR)即将成为在Windows上创立应用程序的主角级基础架构,
多掌握点关于CLR的深浅认识会帮助而构建高速之, 工业级健壮的应用程序.
在当下首文章被, 我们见面浏览,调查CLR的内在精神, 包括对象实例布局,
方法表的布局, 方法分派, 基于接口的分担, 和各种各样的数据结构.

我们会以由C#写成的非常简单的代码示例,
所以任何针对编程语言的隐式引用都是盖C#语言也目标的.
讨论的部分数据结构和算法会在Microsoft® .NET Framework 2.0受改,
但是大部分之概念是勿见面转换的. 我们会动Visual Studio® .NET 2003
Debugger和debugger extension Son of Strike (SOS)来窥探一些数据结构.
SOS能够解CLR内部的数据结构, 能够dump出有因此的信息. 通篇,
我们会谈论在Shared Source CLI(SSCLI)中保有相关兑现的接近, 你可以从
http://msdn.microsoft.com/net/sscli 下充斥至它们.

图表1 会帮助而以寻觅一些结构的时到SSCLI中之信息.

ITEM SSCLI PATH
AppDomain sscliclrsrcvmappdomain.hpp
AppDomainStringLiteralMap sscliclrsrcvmstringliteralmap.h
BaseDomain sscliclrsrcvmappdomain.hpp
ClassLoader sscliclrsrcvmclsload.hpp
EEClass sscliclrsrcvmclass.h
FieldDescs sscliclrsrcvmfield.h
GCHeap sscliclrsrcvmgc.h
GlobalStringLiteralMap sscliclrsrcvmstringliteralmap.h
HandleTable sscliclrsrcvmhandletable.h
InterfaceVTableMapMgr sscliclrsrcvmappdomain.hpp
Large Object Heap sscliclrsrcvmgc.h
LayoutKind sscliclrsrcbclsystemruntimeinteropserviceslayoutkind.cs
LoaderHeaps sscliclrsrcincutilcode.h
MethodDescs sscliclrsrcvmmethod.hpp
MethodTables sscliclrsrcvmclass.h
OBJECTREF sscliclrsrcvmtypehandle.h
SecurityContext sscliclrsrcvmsecurity.h
SecurityDescriptor sscliclrsrcvmsecurity.h
SharedDomain sscliclrsrcvmappdomain.hpp
StructLayoutAttribute sscliclrsrcbclsystemruntimeinteropservicesattributes.cs
SyncTableEntry sscliclrsrcvmsyncblk.h
System namespace sscliclrsrcbclsystem
SystemDomain sscliclrsrcvmappdomain.hpp
TypeHandle sscliclrsrcvmtypehandle.h

在我们开前,请留心:本文提供的音信才针对在X86平台上运行的.NET Framework
1.1立竿见影(对于Shared Source CLI
1.0呢多数适用,只是于一些交互操作的状下得注意例外),对于.NET
Framework
2.0晤产生改,所以恳请不要在构建软件时因让这些内部结构的不变性。

CLR启动程序(Bootstrap)创建的地面

每当CLR执行托管代码的首先实践代码前,会创三单应用程序域。其中有数单对托管代码甚至CLR宿主程序(CLR
hosts)都是不可见的。它们只能由CLR启动进程创造,而提供CLR启动进程的凡shim——mscoree.dll和mscorwks.dll
(在多处理器系统下是mscorsvr.dll)。正而 图2
所示,这些地带是系统域(System Domain)和共享域(Shared
Domain),都是以了么(Singleton)模式。第三个域是缺省应用程序域(Default
AppDomain),它是一个AppDomain的实例,也是绝无仅有的发出命名的地方。对于简易的CLR宿主程序,比如控制台程序,默认的域名由而尽映象文件之名字做。其它的地面可以在托管代码中动用AppDomain.CreateDomain方法创建,或者以非托管的代码中使ICORRuntimeHost接口创建。复杂的宿主程序,比如
ASP.NET,对于特定的网站会依据应用程序的数创建多个域。

图 2 由CLR启动程序创建的域 ↓

图片 1

系统域(System Domain)

系统域负责创建同初始化共享域和默认应用程序域。它将系统库mscorlib.dll载入共享域,并且保护过程范围里边以的涵盖或者显式字符串符号。

字符串驻留(string interning)是 .NET Framework
1.1蒙的一个优化特性,它的处理办法显得有点傻,因为CLR没有受程序集时选择是特性。尽管如此,由于在具备的应用程序域中对一个一定的号子只保留一个对应之字符串,此特性可省去内存空间。

系统域还负责产生过程范围的接口ID,并为此来创造每个应用程序域的接口虚表映射图(InterfaceVtableMaps)的接口。系统域在经过中保持跟踪所有域,并落实加载与卸载应用程序域的意义。

共享域(Shared Domain)

备不属其他特定域的代码被加载到网库SharedDomain.Mscorlib,对于所有应用程序域的用户代码都是少不了的。它会于电动加载到联合享域中。系统命名空间的骨干型,如Object,
ValueType, Array, Enum, String, and
Delegate等等,在CLR启动程序过程中叫事先加载到本域中。用户代码也足以让加载到是域中,方法是以调用CorBindToRuntimeEx时用由CLR宿主程序指定的LoaderOptimization特性。控制台程序也可加载代码到一起享域中,方法是应用System.LoaderOptimizationAttribute特性声明Main方法。共享域还管理一个运基地址作为目录的次第集映射图,此映射图作为管理共享程序集依赖关系的查找表,这些程序集被加载到默认域(DefaultDomain)和外在托管代码中开创的应用程序域。非共享的用户代码被加载到默认域。

默认域(Default Domain)

沉默认域是应用程序域(AppDomain)的一个实例,一般的应用程序代码在中间运行。尽管稍应用程序需要以运作时创造额外的应用程序域(比如有些使用插件,plug-in,架构或者拓展第一之运作时代码生成工作的应用程序),大部分的应用程序在运转中只创造一个所在。所有在此域运行的代码都是于所在层次上发生上下文限制。如果一个应用程序有差不多个应用程序域,任何的域间访问会通过.NET
Remoting代理。额外的域内上下文限制信息可以使System.ContextBoundObject派生的品种创建。每个应用程序域有和好之安康描述符(SecurityDescriptor),安全达成下文(SecurityContext)和默认上下文(DefaultContext),还有团结之加载器堆(高频堆,低频堆和代办堆),句柄表,接口虚表管理器和次集缓存。

加载器堆(Loader Heaps)

加载器堆的意是加载不同的周转时CLR部件和优化在域的周生命期内存在的部件。这些堆的增强基于可预测块,这样好要碎片最小化。加载器堆不同于垃圾回收堆(或者对如多处理器上的大都单堆放),垃圾回收堆保存对象实例,而加载器堆同时保留类型系统。经常看的构件如方法表,方法描述,域描述和接口图,分配在屡次堆上,而于少看的数据结构如EEClass和类加载器及其查找表,分配在低频堆。代理堆保存用于代码访问安全性(code
access security, CAS)的代理部件,如COM封装调用和平台调用(P/Invoke)。

自从赛层次了解域后,我们准备看看她于一个粗略的应用程序的上下文中的情理细节,见
图3。我们在程序运行时停在mc.Method1(),然后用SOS调试器扩展命令DumpDomain来输出域的信息。(请查看
Son of
Strike
问询SOS的加载信息)。这里是编辑后的输出:

图3 Sample1.exe

!DumpDomain
System Domain: 793e9d58, LowFrequencyHeap: 793e9dbc,
HighFrequencyHeap: 793e9e14, StubHeap: 793e9e6c,
Assembly: 0015aa68 [mscorlib], ClassLoader: 0015ab40

Shared Domain: 793eb278, LowFrequencyHeap: 793eb2dc,
HighFrequencyHeap: 793eb334, StubHeap: 793eb38c,
Assembly: 0015aa68 [mscorlib], ClassLoader: 0015ab40

Domain 1: 149100, LowFrequencyHeap: 00149164,
HighFrequencyHeap: 001491bc, StubHeap: 00149214,
Name: Sample1.exe, Assembly: 00164938 [Sample1],
ClassLoader: 00164a78

using System;

public interface MyInterface1
{
    void Method1();
    void Method2();
}
public interface MyInterface2
{
    void Method2();
    void Method3();
}

class MyClass : MyInterface1, MyInterface2
{
    public static string str = "MyString";
    public static uint   ui = 0xAAAAAAAA;
    public void Method1() { Console.WriteLine("Method1"); }
    public void Method2() { Console.WriteLine("Method2"); }
    public virtual void Method3() { Console.WriteLine("Method3"); }
}

class Program
{
    static void Main()
    {
        MyClass mc = new MyClass();
        MyInterface1 mi1 = mc;
        MyInterface2 mi2 = mc;

        int i = MyClass.str.Length;
        uint j = MyClass.ui;

        mc.Method1();
        mi1.Method1();
        mi1.Method2();
        mi2.Method2();
        mi2.Method3();
        mc.Method3();
    }
}

咱的控制台程序,Sample1.exe,被加载到一个称作也”Sample1.exe”的应用程序域。Mscorlib.dll被加载到手拉手享域,不过因它们是核心系统库,所以也在系统域中列有。每个域会分配一个屡屡堆,低频堆和代办堆。系统域和共同享域使用同一之切近加载器,而默认应用程序使用好之近乎加载器。

出口没有出示加载器堆的保留尺寸和已经交给尺寸。高频堆的初始化大小是32KB,每次交4KB。SOS的输出为未曾展示接口虚表堆(InterfaceVtableMap)。每个地区有一个接口虚表堆(简称为IVMap),由自己之加载器堆在域初始化阶段创建。IVMap保留大小是4KB,开始时交由4KB。我们拿会当继续有研究型布局时讨论IVMap的意义。

图2
显示默认的长河堆,JIT代码堆,GC堆(用于小目标)和异常目标堆(用于大小相当于还是过85000字节的对象),它证明了这些堆和加载器堆的语义区别。即时(just-in-time,
JIT)编译器产生x86指令以保留到JIT代码堆中。GC堆和深目标堆是用以托管对象实例化的杂质回收堆。

色原理

种是.NET编程中的骨干单元。在C#惨遭,类型可以应用class,struct和interface关键字展开宣示。大多数类型由程序员显式创建,但是,在特别的彼此操作(interop)情形与长距离对象调用(.NET
Remoting)场合被,.NET
CLR会隐式的出类型,这些有的品类涵盖COM和运行时只是调用封装及传输代理(Runtime
Callable Wrappers and Transparent Proxies)。

咱们通过一个带有对象引用的库开始研究.NET类型原理(典型地,栈是一个对象实例开始生命期的地方)。
图4饱受形的代码包含一个简练的次第,它产生一个控制台的入口点,调用了一个静态方法。Method1创一个SmallClass的类型实例,该类型涵盖一个字节数组,用于演示如何当特别目标堆创建对象。尽管这是同等段子无聊之代码,但是好扶持我们进行座谈。

图4 Large Objects and Small Objects

using System;

class SmallClass
{
    private byte[] _largeObj;
    public SmallClass(int size)
    {
        _largeObj = new byte[size];
        _largeObj[0] = 0xAA;
        _largeObj[1] = 0xBB;
        _largeObj[2] = 0xCC;
    }

    public byte[] LargeObj
    {
        get { return this._largeObj; }
    }
}

class SimpleProgram
{
    static void Main(string[] args)
    {
        SmallClass smallObj = SimpleProgram.Create(84930,10,15,20,25);
        return;
    }

    static SmallClass Create(int size1, int size2, int size3,
        int size4, int size5)
    {
        int objSize = size1 + size2 + size3 + size4 + size5;
        SmallClass smallObj = new SmallClass(objSize);
        return smallObj;
    }
}

图5 显示了停止在Create方法”return smallObj;”
代码行断点时之fastcall栈结构(fastcall时.NET的调用规范,它证明以恐的图景下以函数参数通过寄存器传递,而另参数按照从右到左的次第入栈,然后由让调用函数完成出栈操作)。本地值类型变量objSize内富含在库房结构面临。引用类型变量如smallObj以稳大小(4许节DWORD)保存于栈中,包含了当形似GC堆着分配的靶子的地方。对于人情C++,这是目标的指针;在托管世界面临,它是目标的援。不管怎样,它蕴含了一个目标实例的地址,我们用采取术语对象实例(ObjectInstance)描述对象引用指向地址位置的数据结构。

图5 SimpleProgram的仓库结构与堆

图片 2

一般GC堆上的smallObj对象实例包含一个曰也 _largeObj
的字节数组(注意,图中形的尺寸也85016字节,是实际上的储备大小)。CLR对过或等85000字节的靶子的拍卖及小目标不同。大目标在深目标堆(LOH)上分红,而略带目标在一般GC堆上创立,这样可以优化对象的分配与回收。LOH不见面压缩,而GC堆在GC回收时进行压缩。还有,LOH只见面于全GC回收时于回收。

smallObj的对象实例包含类型句柄(TypeHandle),指向对应档次的方法表。每个声明的色有一个方法表,而同样类型的持有目标实例都对同一个方法表。它蕴含了路的表征信息(接口,抽象类,具体类,COM封装和代理),实现的接口数目,用于接口分派的接口图,方法发明底槽(slot)数目,指向相应实现之槽表。

主意表指向一个叫也EEClass的首要数据结构。在措施发明创建前,CLR类加载器从元数据中创造EEClass。
图4遇,SmallClass的艺术表指向其的EEClass。这些组织指向她的模块和次序集。方法表和EEClass一般分配在共同享域的加载器堆。加载器堆和应用程序域关联,这里提到的数据结构一旦被加载到内,就直到应用程序域卸载时才见面破灭。而且,默认的应用程序域不见面叫卸载,所以这些代码的生存期是截至CLR关闭了。

靶实例

刚刚使我辈说罢的,所有值类型的实例或者隐含在线程栈上,或者隐含在 GC
堆上。所有的援类型在 GC 堆或者 LOH 上创立。图 6
显示了一个杰出的靶子布局。一个靶可以经过以下途径为引用:基于栈的片段变量,在竞相操作还是平台调用情况下的句子柄表,寄存器(执行方时之
this 指针和章程参数),拥有终结器( finalizer )方法的对象的终结器队列。
OBJECTREF 不是依为目标实例的开头位置,而是有一个 DWORD 的偏移量( 4
字节)。此 DWORD 称为对象头,保存一个针对性 SyncTableEntry 表的目录(从 1
开始计数的 syncblk
编号。因为通过索引进行连接,所以于急需增加表的轻重时, CLR
可以以内存中移动是发明。 SyncTableEntry 维护一个反向的已故引用,以便 CLR
可以跟踪 SyncBlock 的所有权。弱引用让 GC
可以以从来不外强引用在时时回收对象。 SyncTableEntry 还保留了一个对准
SyncBlock
的指针,包含了大少用吃一个目标的具有实例使用的实用之信息。这些消息包括对象锁,哈希编码,任何移层
(thunking) 数据以及应用程序域的目。对于多数的对象实例,不会见呢实际的
SyncBlock 分配内存,而且 syncblk 编号为 0 。这同一碰在执行线程遇到如
lock(obj) 或者 obj.GetHashCode 的讲话时会发生变化,如下所示:

SmallClass obj = new SmallClass()
// Do some work here
lock(obj) { /* Do some synchronized work here */ }
obj.GetHashCode();

图 6 对象实例布局
图片 3

在上述代码中, smallObj 会使 0 作为它们的胚胎之 syncblk 编号。 lock
语词使得 CLR 创建一个 syncblk 入口并采取相应的数值更新对象头。因为 C#
的 lock 关键字会扩展为 try-finally 语句并采用 Monitor 类,一个用作同步的
Monitor 对象在 syncblk 上创设。堆 GetHashCode
的调用会以对象的哈希编码增加 syncblk 。
以 SyncBlock 中出另外的域,它们于 COM 交互操作与封送委托( marshaling
delegates )到非托管代码时采用,不过这跟典型的靶子用处无关。
项目句柄紧跟以目标实例中的 syncblk
编号后。为了保全连续性,我会以验证实例变量后讨论类型句柄。实例域(
Instance field
)的变量列表紧跟以项目句柄后。默认情况下,实例域会以内存最可行运用的方排列,这样才需要极少之当对合之填充充字节。
7
的代码显示了 SimpleClass 包含有局部例外尺寸的实例变量。

图 7 SimpleClass with Instance Variables

class SimpleClass
{
    private byte b1 = 1;                // 1 byte
    private byte b2 = 2;                // 1 byte
    private byte b3 = 3;                // 1 byte
    private byte b4 = 4;                // 1 byte
    private char c1 = 'A';              // 2 bytes
    private char c2 = 'B';              // 2 bytes
    private short s1 = 11;              // 2 bytes
    private short s2 = 12;              // 2 bytes
    private int i1 = 21;                // 4 bytes
    private long l1 = 31;               // 8 bytes
    private string str = "MyString"; // 4 bytes (only OBJECTREF)

    //Total instance variable size = 28 bytes 

    static void Main()
    {
        SimpleClass simpleObj = new SimpleClass();
        return;
    }
}

图 8 显示了在 Visual Studio 调试器之内存窗口中之一个 SimpleClass
对象实例。我们当图 7 的 return 语句处设置了断点,然后用 ECX
寄存器保存的 simpleObj 地址在内存窗口亮对象实例。前 4 只字节是 syncblk
编号。因为我们尚无因此另外共同代码用是实例(也远非看它的哈希编码),
syncblk 编号为 0 。保存于栈变量的对象实例,指为起始位置的 4
个字节的偏移处。字节变量 b1,b2,b3 和 b4 被一个对接一个之排于一齐。两只
short 类型变量 s1 和 s2 也给列于联合。字符串变量 str 是一个 4 字节的
OBJECTREF ,指向 GC
堆中分配的莫过于的字符串实例。字符串是一个专程的门类,因为有着包含同样仿标记的字符串,会在先后集加载到过程时对一个大局字符串表的同等实例。这个进程叫字符串驻留(
string interning ),设计目的是优化内存的使。我们之前曾提过,在 NET
Framework 1.1 中,程序集不可知选择是否采取是过程,尽管未来本的 CLR
可能会见供这么的力。

图 8 Debugger Memory Window for Object Instance
图片 4

因而默认情况下,成员变量在源代码中的词典顺序没有在内存中保持。在相互操作的景况下,词典顺序必须为保留到外存中,这时可以采用
StructLayoutAttribute 特性,它来一个 LayoutKind 的枚举类型作为参数。
LayoutKind.Sequential 可以吧叫封送( marshaled
)数据保持词典顺序,尽管当 .NET Framework 1.1
中,它并未影响托管的布局(但是 .NET Framework 2.0
可能会见这样做)。在互动操作的情形下,如果您真用分外的填充字节和展示的控制域的次第,
LayoutKind.Explicit 可以和域层次的 FieldOffset 特性一起行使。

圈了脚的内存内容后,我们应用 SOS 看看对象实例。一个立竿见影的下令是
DumpHeap
,它可以列出所有的堆内容以及一个专程类型的保有实例。无需依靠寄存器,
DumpHeap 可以显示我们创建的唯一一个实例的地址。

!DumpHeap -type SimpleClass
Loaded Son of Strike data table version 5 from
"C:WINDOWSMicrosoft.NETFrameworkv1.1.4322mscorwks.dll"
 Address       MT     Size
00a8197c 00955124       36
Last good object: 00a819a0
total 1 objects
Statistics:
      MT    Count TotalSize Class Name
  955124        1        36 SimpleClass

目标的究竟大小是 36 字节,不管字符串多格外, SimpleClass 的实例只包含一个
DWORD 的目标引用。 SimpleClass 的实例变量只占用 28 字节,其它 8
只字节包括项目句柄( 4 字节)和 syncblk 编号( 4 字节)。找到 simpleObj
实例的地方后,我们得采取 DumpObj 命令输出它的内容,如下所示:

!DumpObj 0x00a8197c
Name: SimpleClass
MethodTable 0x00955124
EEClass 0x02ca33b0
Size 36(0x24) bytes
FieldDesc*: 00955064
      MT    Field   Offset                 Type       Attr    Value Name
00955124  400000a        4         System.Int64   instance      31 l1
00955124  400000b        c                CLASS   instance 00a819a0 str
    &lt;&lt; some fields omitted from the display for brevity &gt;&gt;
00955124  4000003       1e          System.Byte   instance        3 b3
00955124  4000004       1f          System.Byte   instance        4 b4

刚巧使前说罢, C# 编译器对于接近的默认布局使用 LayoutType.Auto
(对于组织使 LayoutType.Sequential
);因此类加载器重新排列实例域以无限小化填充字节。我们可以用 ObjSize
来输出包含被 str 实例占用的上空,如下所示:

!ObjSize 0x00a8197c
sizeof(00a8197c) =       72 (    0x48) bytes (SimpleClass)

倘您于目标图的全局大小( 72 字节)减去 SimpleClass 的大小( 36
字节),就得获得 str 的轻重缓急,即 36 字节。让我们输出 str
实例来说明这个结果:

!DumpObj 0x00a819a0
Name: System.String
MethodTable 0x009742d8
EEClass 0x02c4c6c4
Size 36(0x24) bytes

设您拿字符串实例的深浅(36字节)加上SimpleClass实例的轻重(36字节),就得取ObjSize命令语的到底大小72字节。

恳请留心ObjSize不含syncblk结构占用的内存。而且,在.NET Framework
1.1遇,CLR不掌握非托管资源占用的内存,如GDI对象,COM对象,文件句柄等等;因此它们不见面吃这命令语。

本着方法发明的品种句柄在syncblk编号后分配。在目标实例创建前,CLR查看加载类型,如果无找到,则进行加载,获得方法表地址,创建对象实例,然后将品种句柄值追加至对象实例中。JIT编译器产生的代码在开展方式分派时采取类句柄来稳定方法表。CLR于待史可以由此艺术表反向顾加载类型时采用项目句柄。

Son of Strike
SOS调试器扩展程序用于本文化的显示CLR数据结构的情节,它是 .NET
Framework 安装程序的一致片段,位于
%windir%\Microsoft.NET\Framework\v1.1.4322。SOS加载到过程之前,在
Visual Studio 中启用托管代码调试。 添加 SOS.dll
所于的文本夹到PATH环境变量中。 加载 SOS.dll, 然后安装一个断点, 打开
Debug|Windows|Immediate。然后以 Immediate 窗口被实行 .load
sos.dll。使用 !help
获取调试相关的组成部分指令,关于SOS更多信息,参考这里。

方法表

每个接近及实例在加载到应用程序域时,会以内存中经过措施表来表示。这是于靶的第一只实例创建前的切近加载活动之结果。对象实例表示的凡状态,而艺术发明表示了作为。通过EEClass,方法表把对象实例绑定到让语言编译器产生的投到内存的排头数据结构(metadata
structures)。方法发明包含的音信与外挂的音讯可经过System.Type访问。指向方法发明的指针在托管代码中好透过Type.RuntimeTypeHandle属性获得。对象实例包含的色句柄指向方法发明开位置的摇处,偏移量默认情况下是12字节,包含了GC信息。我们无打算在此地针对那个进行座谈。

图 9
显示了章程发明的出众布局。我们见面证明项目句柄的片段最主要的域,但是于截然的列表,请参见此图。让我们于基实例大小(Base
Instance Size)开始,因为它直接关联及运行时之内存状态。

图 9 方法表布局

图片 5

基实例大小

基实例大小是出于类似加载器计算的对象的分寸,基于代码中声称的地域。之前曾经讨论过,当前GC的贯彻内需一个足足12字节的对象实例。如果一个类没有定义任何的例域,它起码含有额外的4独字节。其它的8独字节被指向象头(可能含有syncblk编号)和类型句柄占用。再说一不成,对象的尺寸会惨遭StructLayoutAttribute的熏陶。

看看图3遇显得的MyClass(有三三两两单接口)的方发明底内存快照(Visual
Studio .NET
2003舅存窗口),将它们跟SOS的出口进行比。在图9屡遭,对象大小在4字节的摆处,值吗12(0x0000000C)字节。以下是SOS的DumpHeap命令的出口:

!DumpHeap -type MyClass
 Address       MT     Size
00a819ac 009552a0       12
total 1 objects
Statistics:
    MT  Count TotalSize Class Name
9552a0      1        12    MyClass

法槽表(Method Slot Table)

在点子发明中蕴含了一个槽表,指向各个艺术的描述(MethodDesc),提供了档次的行为能力。方法槽表是根据方法实现的线性链表,按照如下顺序排列:继承的虚方法,引入的虚方法,实例方法,静态方法。

好像加载器在此时此刻好像,父类和接口的首批数据中遍历,然后创建方法表。在列过程遭到,它替换所有的吃埋的虚方法和吃藏的父类方法,创建新的扇,在得时复制槽。槽复制是必不可少的,它可以吃每个接口有协调的最小之vtable。但是被复制的槽指向同的情理实现。MyClass包含接口方法,一个类似构造函数(.cctor)和对象构造函数(.ctor)。对象构造函数由C#编译器为有没有来显式定义构造函数的靶子自动生成。因为咱们定义并初始化了一个静态变量,编译器会转变一个近乎构造函数。图10显了MyClass的方法发明的布局。布局显示了10只措施,因为Method2槽为接口IVMap进行了复制,下面我们会展开座谈。图11展示了MyClass的法门发明的SOS的出口。

图10 MyClass MethodTable Layout
图片 6

图11 SOS Dump of MyClass Method Table

!DumpMT -MD 0x9552a0
  Entry  MethodDesc  Return Type       Name
0097203b 00972040    String            System.Object.ToString()
009720fb 00972100    Boolean           System.Object.Equals(Object)
00972113 00972118    I4                System.Object.GetHashCode()
0097207b 00972080    Void              System.Object.Finalize()
00955253 00955258    Void              MyClass.Method1()
00955263 00955268    Void              MyClass.Method2()
00955263 00955268    Void              MyClass.Method2()
00955273 00955278    Void              MyClass.Method3()
00955283 00955288    Void              MyClass..cctor()
00955293 00955298    Void              MyClass..ctor()

其余类型的起来4单主意总是ToString, Equals, GetHashCode, and
Finalize。这些是于System.Object继承的虚方法。Method2槽被进行了复制,但是都指向相同之不二法门描述。代码显示定义的.cctor和.ctor会分别同静态方法与实例方法分在同样组。

计描述(MethodDesc)

艺术描述(MethodDesc)是CLR知道的主意实现的一个包裹。有几栽档次的章程描述,除了用于托管实现,分别用于不同之竞相操作实现的调用。在本文中,我们才考察图3代码中之托管方描述。方法描述在接近加载过程被生出,初始化为指向IL。每个方法描述包含一个预编译代理(PreJitStub),负责触发JIT编译。图12展示了一个杰出的布局,方法发明底槽实际上对代理,而无是实在的法描述数据结构。对于实际的道描述,这是-5字节的撼动,是每个方法的8个附加字节的同等局部。这5只字节包含了调用预编译代理程序的吩咐。5字节之皇可以于SOS的DumpMT输出从观望,因为方法描述总是方法槽表指向的位置后的5只字节。在第一不行调动用时,会调用JIT编译程序。在编译完成后,包含调用指令的5只字节会被超反至JIT编译后的x86代码的义务跳转指令覆盖。

图 12方式描述

图片 7

图12的道表槽指向的代码进行反汇编,显示了对预编译代理的调用。以下是以
Method2 被JIT编译前的相反汇编的简化显示。

Method2:

!u 0x00955263
Unmanaged code
00955263 call        003C3538        ;call to the jitted Method2()
00955268 add         eax,68040000h   ;ignore this and the rest
                                     ;as !u thinks it as code

今日咱们执行之方,然后倒汇编相同之地点:

!u 0x00955263
Unmanaged code
00955263 jmp     02C633E8        ;call to the jitted Method2()
00955268 add     eax,0E8040000h  ;ignore this and the rest
                                 ;as !u thinks it as code

于这地点,只有开始5只字节是代码,剩余字节包含了Method2的点子描述的数目。“!u”命令不明白就或多或少,所以生成的是烂的代码,你得忽略5个字节后底具备东西。

CodeOrIL在JIT编译前带有IL中艺术实现之相对虚地址(Relative Virtual
Address
,RVA)。此域用作标志,表示是否IL。在依照要求编译后,CLR用编译后底代码地址更新此域。让咱于列有底函数中甄选一个,然后据此DumpMT命令分别出口在JIT编译前后的主意描述的始末:

!DumpMD 0x00955268
Method Name : [DEFAULT] [hasThis] Void MyClass.Method2()
MethodTable 9552a0
Module: 164008
mdToken: 06000006
Flags : 400
IL RVA : 00002068

编译后,方法描述的内容如下:

!DumpMD 0x00955268
Method Name : [DEFAULT] [hasThis] Void MyClass.Method2()
MethodTable 9552a0
Module: 164008
mdToken: 06000006
Flags : 400
Method VA : 02c633e8

方式的是标志域的编码包含了法子的路,例如静态,实例,接口方法或者COM实现。让咱看方法表另外一个繁杂的方面:接口实现。它包裹了布局过程具有的错综复杂,让托管环境看就一点看押起大概。然后,我们将说明接口如何进行布局和因接口的主意分派的适当工作章程。

接口虚表图和接口图(Interface Vtable Map and Interface Map)

当章程发明的第12字节偏移处是一个最主要之指针,接口虚表(IVMap)。如图9所示,接口虚表指向一个应用程序域层次的映射表,该表以进程层次之接口ID作为目录。接口ID在接口类型第一不善加载时创造。每个接口的兑现还当接口虚表中发出一个记下。如果MyInterface1被简单个像样实现,在接口虚表表中即使生少数只记录。该记录会反向指向MyClass方法发明内含的子表的启位置,如图9所示。这是接口方法分派发生时以的援。接口虚表是依据方法发明内含的接口图信息创建,接口图在术发明布局过程中冲类的首数据创建。一旦类型加载成功,只有接口虚表用于方法分派。

第28字节位置的接口图会指向内含在章程表中的接口信息记录。在这种情况下,对MyClass实现之少只接口中之各国一个都起星星点点条记下。第一久接口信息记录的初始4单字节指向MyInterface1的档次句柄(见图9图10)。接着的WORD(2字节)被一个标明占用(0象征从今父类派生,1象征是因为时相近实现)。在表明后的WORD是一个初步槽(Start
Slot),被类似加载器用来布局接口实现的子表。对于MyInterface2,开始槽的价为4(从0开始编号),所以槽5和6据为实现;对于MyInterface2,开始槽的价值吗6,所以槽7和8赖为实现。类加载器会于用常复制槽来来这样的法力:每个接口有谈得来之落实,然而物理映射到同一的方式描述。在MyClass中,MyInterface1.Method2及MyInterface2.Method2会指向相同的贯彻。

依据接口的法门分派通过接口虚表进行,而直接的章程分派通过保留在各个槽的法描述地址进行。如前提及,.NET框架下fastcall的调用约定,最先2个参数在可能的时光一般通过ECX和EDX寄存器传递。实例方法的首先只参数总是this指针,所以经过ECX寄存器传送,可以当“mov
ecx,esi”语句看到这一点:

mi1.Method1();
mov    ecx,edi                 ;move "this" pointer into ecx
mov    eax,dword ptr [ecx]     ;move "TypeHandle" into eax
mov    eax,dword ptr [eax+0Ch] ;move IVMap address into eax at offset 12
mov    eax,dword ptr [eax+30h] ;move the ifc impl start slot into eax
call   dword ptr [eax]         ;call Method1

mc.Method1();
mov    ecx,esi                 ;move "this" pointer into ecx
cmp    dword ptr [ecx],ecx     ;compare and set flags
call   dword ptr ds:[009552D8h];directly call Method1

这些反汇编显示了直接调用MyClass的实例方法没有动用偏移。JIT编译器把法描述的地址直接写到代码中。基于接口的分担通过接口虚表发生,和一直分派相比要部分分外的通令。一个命用来得到接口虚表的地点,另一个落方式槽表中之接口实现之起槽。而且,把一个靶实例转换为接口就待拷贝this指针到目标的变量。在觊觎2备受,语句“mi1=mc”使用一个指令把mc的对象引用拷贝到mi1。

虚分派(Virtual Dispatch)

兹咱们省虚分派,并且和根据接口的分担进行比。以下是图3中MyClass.Method3的心虚函数调用的反汇编代码:

mc.Method3();
Mov    ecx,esi               ;move "this" pointer into ecx
Mov    eax,dword ptr [ecx]   ;acquire the MethodTable address
Call   dword ptr [eax+44h]   ;dispatch to the method at offset 0x44

虚分派总是通过一个原则性的槽编号发生,和措施表指针在一定的类(类型)实现层次无关。在方式发明布局时,类加载器用覆盖的子类的贯彻代替父类的落实。结果,对大对象的办法调用被分摊到子对象的兑现。反汇编显示了分派通过8声泪俱下槽发生,可以当调试器的内存窗口(如图10所显示)和DumpMT的输出看到就或多或少。

静态变量(Static Variables)

静态变量是方法表数据结构的要紧片段。作为艺术发明的同等局部,它们分配在措施发明底槽数组后。所有的原始静态类型是内联的,而对此组织和援的型的静态值对象,通于句柄表中开创的靶子引用来针对。方法表中的对象引用指向应用程序域的语句柄表的目标引用,它引用了堆积如山上创设的对象实例。一旦创立后,句柄表内的目标引用会使堆上的靶子实例保持在,直到应用程序域于卸载。在图9
中,静态字符串变量str指于句柄表的对象引用,后者对GC堆上之MyString。

EEClass

EEClass在点子发明创建前开在,它跟办法发明组成起来,是种类声明的CLR版本。实际上,EEClass和方表逻辑上是一个数据结构(它们并表示一个列),只不过因为以频度的不同而被分别。经常应用的域放在方法表,而未常利用的地区于EEClass中。这样,需要被JIT编译函数使用的信息(如名字,域和摇头)在EEClass中,但是运行时索要的音信(如虚表槽和GC信息)在术表中。

对各个一个路会加载一个EEClass到应用程序域中,包括接口,类,抽象类,数组和结构。每个EEClass是一个吃实践引擎跟踪的养之节点。CLR使用此网络在EEClass结构中浏览,其目的包括类加载,方法发明布局,类型验证和类型转换。EEClass的子-父关系因继承层次建立,而父-子关系因接口层次以及类加载顺序的三结合。在实行托管代码的进程被,新的EEClass节点被投入,节点的关系为补充,新的关系被树。在网络中,相邻的EEClass还有一个水平的涉嫌。EEClass有三只域用于管理为加载类型的节点关系:父类(Parent
Class),相邻链(sibling chain)和子链(children
chain)。关于图4吃之MyClass上下文中之EEClass的语义,请参见图13

图13偏偏显示了跟之讨论有关的一些域。因为我们忽视了布局中之片地区,我们无当祈求被相当显示偏移。EEClass有一个间接的对艺术发明的援。EEClass也本着于默认应用程序域的勤堆分配的不二法门描述块。在艺术发明创建时,对经过堆上分红的地方描述列表的一个援提供了域的布局信息。EEClass在应用程序域的低频堆分配,这样操作系统可以更好之展开内存分页管理,因此减少了办事集。

图13 EEClass 布局

图片 8

图13遭之其它域在MyClass(图3)的上下文的意义不讲自明。我们本探访用SOS输出的EEClass的确实的物理内存。在mc.Method1替码行设置断点后,运行图3的先后。首先使命令Name2EE获得MyClass的EEClass的地址。

!Name2EE C:WorkingtestClrInternalsSample1.exe MyClass

MethodTable: 009552a0
EEClass: 02ca3508
Name: MyClass

Name2EE的首先个参数时模块名,可以起DumpDomain命令得到。现在咱们得到了EEClass的地方,我们输出EEClass:

!DumpClass 02ca3508
Class Name : MyClass, mdToken : 02000004, Parent Class : 02c4c3e4
ClassLoader : 00163ad8, Method Table : 009552a0, Vtable Slots : 8
Total Method Slots : a, NumInstanceFields: 0,
NumStaticFields: 2,FieldDesc*: 00955224

      MT    Field   Offset  Type           Attr    Value    Name
009552a0  4000001   2c      CLASS          static 00a8198c  str
009552a0  4000002   30      System.UInt32  static aaaaaaaa  ui

图13与DumpClass的出口看起了相同。元数据令牌(metadata
token,mdToken)表示了于模块PE文件中映射到内存的元数据表的MyClass索引,父类指向System.Object。从相互邻链指为名也Program的EEClass,可以解贪图13显示的凡加载Program时之结果。

MyClass有8个虚表槽(可以叫虚分派的章程)。即使Method1和Method2休是虚方法,它们可当经接口进行摊派时受认为是虚函数并参加到列表中。把.cctor和.ctor加入到列表中,你见面得总共10个点子。最后列有底凡近乎的蝇头只静态域。MyClass没有实例域。其它地方不出口自明。

结论

咱关于CLR一些极其要害的内在的追旅程算终止了。显然,还有不少题材亟需涉及,而且需要以重复怪的层次上讨论,但是咱盼望马上得辅助您望事物如何行事。这里提供的森的音信或者会见在.NET框架和CLR的新兴版被改变,不过尽管本文提到的CLR数据结构可能改,概念应该维持不移。

网站地图xml地图