YARP, 强大的可编程的反向代理

YARP, 在Nginx、Apache、Ocelot等之外,一个.Net Core下Reverse Proxy的新起之秀,Microsoft官方维护开源,从此你拥有了强大的可编程的反向代理。

原文链接:YARP Documentation (microsoft.github.io)

YARP is a library to help create reverse proxy servers that are high-performance, production-ready, and highly customizable. Right now it’s still in preview, but please provide us your feedback by going to the GitHub repository.

We found a bunch of internal teams at Microsoft who were either building a reverse proxy for their service or had been asking about APIs and tech for building one, so we decided to get them all together to work on a common solution, this project.

YARP is built on .NET using the infrastructure from ASP.NET and .NET (.NET Core 3.1 and .NET 5.0). The key differentiator for YARP is that it’s been designed to be easily customized and tweaked via .NET code to match the specific needs of each deployment scenario.

We expect YARP to ship as a library, project template, and a single-file exe, to provide a variety of choices for building a robust, performant proxy server. Its pipeline and modules are designed so that you can then customize the functionality for your needs. For example, while YARP supports configuration files, we expect that many users will want to manage the configuration programmatically based on their own configuration management system, YARP will provide a configuration API to enable that customization in-proc. YARP is designed with customizability as a primary scenario rather than requiring you to break out to script or rebuild the library from source.


  • Header Routing

Proxy routes specified in config or via code must include at least a path or host to match against. In addition to these, a route can also specify one or more headers that must be present on the request.

  • Authentication and Authorization

The reverse proxy can be used to authenticate and authorize requests before they are proxied to the destination servers. This can reduce load on the destination servers, add a layer of protection, and ensure consistent policies are implemented across your applications.

  • Cross-Origin Requests (CORS)

The reverse proxy can handle cross-origin requests before they are proxied to the destination servers. This can reduce load on the destination servers and ensure consistent policies are implemented across your applications.

  • Session Affinity:

Session affinity is a mechanism to bind (affinitize) a causally related request sequence to the destination handled the first request when the load is balanced among several destinations. It is useful in scenarios where the most requests in a sequence work with the same data and the cost of data access differs for different nodes (destinations) handling requests. The most common example is a transient caching (e.g. in-memory) where the first request fetches data from a slower persistent storage into a fast local cache and the others work only with the cached data thus increasing throughput.

  • Load Balancing

Whenever there are multiple healthy destinations available, YARP has to decide which one to use for a given request. YARP ships with built-in load-balancing algorithms, but also offers extensibility for any custom load balancing approach.

  • Transforms

When proxying a request it’s common to modify parts of the request or response to adapt to the destination server’s requirements or to flow additional data such as the client’s original IP address. This process is implemented via Transforms. Types of transforms are defined globally for the application and then individual routes supply the parameters to enable and configure those transforms. The original request objects are not modified by these transforms, only the proxy requests.

  • Destinations Health Checks

In most of the real-world systems, it’s expected for their nodes to occasionally experience transient issues and go down completely due to a variety of reasons such as an overload, resource leakage, hardware failures, etc. Ideally, it’d be desirable to completely prevent those unfortunate events from occurring in a proactive way, but the cost of designing and building such an ideal system is generally prohibitively high. However, there is another reactive approach which is cheaper and aimed to minimizing a negative impact failures cause on client requests. The proxy can analyze each nodes health and stop sending client traffic to unhealthy ones until they recover. YARP implements this approach in the form of active and passive destination health checks.

Continue reading “YARP, 强大的可编程的反向代理”

ocelot brief

The article copyed from https://ocelot.readthedocs.io

Ocelot is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system.

In particular I want easy integration with IdentityServer reference and bearer tokens.

Ocelot is a bunch of middlewares in a specific order.

Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features.

The following are configurations that you use when deploying Ocelot.

Basic Implementation


With IdentityServer


Multiple Instances


Continue reading “ocelot brief”




1. COM中的公寓

1.1 基本规则









1.2 公寓类型匹配


  1. 组件公寓模型是在组件注册到注册表时设定,通过组件公寓模型,组件声明自己可以住在什么样的公寓里。可选项包括:Apartment,Free和Both。Apartment,我只能住在单线程公寓中;Free,我只能住在多线程公寓中;Both,我随意,单线程公寓或多线程公寓都可以。
  2. 客户端线程公寓模型就是线程的公寓模型,表示当前线程提供什么样的公寓。可选项包括:单线程公寓(STA)或多线程公寓(MTA),也就是本文所讨论的STAThread和MTAThread。


客户端线程公寓模型 \ 组件公寓模型  Apartment Free Both




1.3 .NET中设置客户端线程公寓模型 


在.NET中可以通过STAThread和MTAThread属性来设置主线程的公寓类型, 通过Thread.SetApartmentState可以设置工作线程的公寓类型。



2. 一个简单的COM组件

为了演示单线程公寓和多线程公寓的区别,我们用ATL实现定义一个简单的COM组件SimpleCom,该组件包含一个返回字符串的方法Hello,返回的字符串分三步合成,每步之间通过Consume方法来消耗较长CPU周期,确保Hello不会在操作系统的一个时间片内被执行完成,保证Hello函数被并发执行,以达到演示的效果。代码如下: Continue reading “STA and MTA”



1. 演变历史

它们是按照以下顺序逐步出现的,史前-> ODBC-> OLEDB-> ADO-> ADO.Net。

看看Wiki上的MDAC定义:“Microsoft Data Access Components(MDAC)是微软专门为数据访问功能而发展的应用程序开发接口,做为微软的统一化数据访问(Universal Data Access; UDA)解决方案的核心组成,最初的版本在1996年时发表,其组成组件有ODBC,OLE DB以及ADO,其中ADO是在Visual Basic上唯一的数据访问管道,而OLE DB则是基于COM之上,供C/C++ 访问与提供数据的接口,ODBC则是统一化的数据访问API。”

也就是说,ODBC, OLE DB, ADO都是这个解决方案的组件。

再看看这张调用图,很多事情就不言自明了。(其实我觉得这图有些不准确,.Net Managed Provider应该有条专线直通Data Stores, 这样才能体现出Sql Server的性能优越性嘛…)
Continue reading “ODBC, OLEDB, ADO, ADO.Net简史”


  1. Microsoft OData stack
  2. ODataLib
  3. RESTier
  4. OData v4 Web API

原文来自: http://odata.github.io/odata.net/


  • 1.1 Write OData Payload

    There are several kinds of OData payload, includes service document, model metadata, feed, entry, entity references(s), complex value(s), primitive value(s). OData Core library is designed to write and read all these payloads.We’ll go through each kind of payload here. But first, we’ll set up the neccessary code that is common to all kind of payload.Class ODataMessageWriter is the entrance class to write the OData Payload.To construct an ODataMessageWriter instance, you’ll need to provide an IODataResponseMessage, or IODataRequestMessage, depends on if you are writing a response or a request.OData Core library provides no implementation of these two interfaces, because it is different in different scenario.In this tutoria, we’ll use the InMemoryMessage.cs.

    We’ll use the model set up in the EDMLIB section.

    IEdmModel model = builder

    Then set up the message to write the payload.

    MemoryStream stream = new MemoryStream();
    InMemoryMessage message = new InMemoryMessage() {Stream = stream};

    Create the settings:

    ODataMessageWriterSettings settings = new ODataMessageWriterSettings();

    Now we are ready to create the ODataMessageWriter instance:

    ODataMessageWriter writer = new ODataMessageWriter((IODataResponseMessage) message, settings, model);

    After we write the payload, we can inspect into the memory stream wrapped in InMemoryMessage to check what is written.

    string output =Encoding.UTF8.GetString(stream.ToArray());

    Here is the whole program that use SampleModelBuilder and InMemoryMessage to write metadata payload:

    IEdmModel model = builder
                MemoryStream stream = new MemoryStream();
                InMemoryMessage message = new InMemoryMessage() {Stream = stream};
                ODataMessageWriterSettings settings = new ODataMessageWriterSettings();
                ODataMessageWriter writer = new ODataMessageWriter((IODataResponseMessage) message, settings, model);
                string output =Encoding.UTF8.GetString(stream.ToArray());

    Now we’ll go through on each kind of payload.

    Write metadata

    Write metadata is simple, just use WriteMetadataDocument method in ODataMessageWriter.


    Please be noticed that this API only works when: 1. Writting response message, that means when constructing the ODataMessageWriter, you mut supply IODataRequestMessage. 2. A model is supplied when constructing ODataMessageWriter.

    So the following two examples won’t work.

    ODataMessageWriter writer = new ODataMessageWriter((IODataRequestMessage) message, settings, model);
    ODataMessageWriter writer = new ODataMessageWriter((IODataResponseMessage) message);

    Write service document

    To write a service document, first create a ODataServiceDocument instance, which will contains all the neccessary information in a service document, that include, entity set, singleton and function import.

    In this example, we create a service document that contains two entity sets, one singleton and one function import.

    ODataServiceDocument serviceDocument = new ODataServiceDocument();
                serviceDocument.EntitySets = new []
                    new ODataEntitySetInfo
                        Name = "Customers",
                        Title = "Customers",
                        Url = new Uri("Customers", UriKind.Relative),
                    new ODataEntitySetInfo
                        Name = "Orders",
                        Title = "Orders",
                        Url = new Uri("Orders", UriKind.Relative),
                serviceDocument.Singletons = new[]
                    new ODataSingletonInfo
                        Name = "Company",
                        Title = "Company",
                        Url = new Uri("Company", UriKind.Relative),
                serviceDocument.FunctionImports = new[]
                    new ODataFunctionImportInfo
                        Name = "GetOutOfDateOrders",
                        Title = "GetOutOfDateOrders",
                        Url = new Uri("GetOutOfDateOrders", UriKind.Relative),

    Then let’s call WriteServiceDocument method to write it.


    However, this would not work. An ODataException will threw up said that “The ServiceRoot property in ODataMessageWriterSettings.ODataUri must be set when writing a payload.” This is because a valid service document will contains a context url reference to the metadata url, which need to be told in ODataMessageWriterSettings.

    This service root informaiton is provided in ODataUri.ServiceRoot, as this code shows.

    ODataMessageWriterSettings settings = new ODataMessageWriterSettings();
                settings.ODataUri = new ODataUri()
                    ServiceRoot = new Uri("http://services.odata.org/V4/OData/OData.svc/")
                ODataMessageWriter writer = new ODataMessageWriter((IODataResponseMessage) message, settings);

    As you can see, you don’t need to provide model to write service document.

    It is a little work to instantiate the service document instance and set up the entity sets, singletons and function imports. Actually, the EdmLib provided a useful API which can generate a service document instance from model. The API is named GenerateServiceDocument, and defined as an extension method on IEdmModel.

    ODataServiceDocument serviceDocument = model.GenerateServiceDocument();

    All the entity sets, singletons and function imports whose IncludeInServiceDocument attribute is set to true in the model will be in the generated service document. And according to the spec, only those function import without any parameter should set its IncludeInServiceDocument attribute to true.

    And as WriteMetadata API, WriteServiceDocument works only when it is writing a response message.

    Besides API WriteServiceDocument, there is another API called WriteServiceDocumentAsync in ODataMessageWriter class. It is an async version of WriteServiceDocument, so you can call it in async way.

    await writer.WriteServiceDocumentAsync(serviceDocument);

    A lot of API in writer and reader provides async version of API, they all work as a async complement of the API that without Async suffix.

    Write Feed

    Collection of entities is called feed in OData Core Library. Unlike metadata or service document, you must create another writer on ODatMessageWriter to write the feed. The library is designed to write feed in an streaming way, which means the entry is written one by one.

    Feed is represented by ODataFeed class. To write a feed, following information are needed: 1. The service root, which is defined by ODataUri. 2. The model, as construct parameter of ODataMessageWriter. 3. Entity set and entity type information.

    Here is how to write an empty feed.

    ODataMessageWriterSettings settings = new ODataMessageWriterSettings();
                settings.ODataUri = new ODataUri()
                    ServiceRoot = new Uri("http://services.odata.org/V4/OData/OData.svc/")
                ODataMessageWriter writer = new ODataMessageWriter((IODataResponseMessage)message, settings, model);
                IEdmEntitySet entitySet = model.FindDeclaredEntitySet("Customers");
                ODataWriter odataWriter = writer.CreateODataFeedWriter(entitySet);
                ODataFeed feed = new ODataFeed();

    Line 4 give the service root, line 6 give the model, and line 10 give the entity set and entity type information.

    The output of it looks like this.


    The output contains a context url in the output, which is based on the service root you provided in ODataUri, and the entity set name. There is also a value which is an empty collection, where will hold the entities if there is any.

    There is another way to provide the entity set and entity type information, through ODataFeedAndEntrySerializationInfo, and in this no model is needed.

    ODataMessageWriterSettings settings = new ODataMessageWriterSettings();
                settings.ODataUri = new ODataUri()
                    ServiceRoot = new Uri("http://services.odata.org/V4/OData/OData.svc/")
                ODataMessageWriter writer = new ODataMessageWriter((IODataResponseMessage)message, settings);
                ODataWriter odataWriter = writer.CreateODataFeedWriter();
                ODataFeed feed = new ODataFeed();
                feed.SetSerializationInfo(new ODataFeedAndEntrySerializationInfo()
                    NavigationSourceName = "Customers",
                    NavigationSourceEntityTypeName = "Customer"

    When writting feed, you can provide a next page, which is used in server driven paging.

    ODataFeed feed = new ODataFeed();
                feed.NextPageLink = new Uri("Customers?next", UriKind.Relative);

    The output will contains a next link before the value collection.


    If you want the next link to be appear after the value collection, you can set the next link after the WriteStart call, before the WriteEnd call.

    ODataFeed feed = new ODataFeed();
                feed.NextPageLink = new Uri("Customers?next", UriKind.Relative);

    There is no rule on next link, as long as it is a valid url.

    To write entry in the feed, create the ODataEntry instance and call WriteStart and WriteEnd on it between the WriteStart and WriteEnd call of feed.

    ODataFeed feed = new ODataFeed();
                ODataEntry entry = new ODataEntry()
                    Properties = new[]
                        new ODataProperty()
                            Name = "Id",
                            Value = 1,
                        new ODataProperty()
                            Name = "Name",
                            Value = "Tom",

    We’ll introduce more details on writting entry in next section.

    Write Entry

    Entry can be written in several places: 1. As the top level entry. 2. As the entry in a feed. 3. As the entry expanded an other entry.

    To write a top level entry, use ODataMessageWriter.CreateEntryWriter.

    ODataMessageWriter writer = new ODataMessageWriter((IODataResponseMessage)message, settings, model);
                IEdmEntitySet entitySet = model.FindDeclaredEntitySet("Customers");
                ODataWriter odataWriter = writer.CreateODataEntryWriter(entitySet);
                ODataEntry entry = new ODataEntry()
                    Properties = new[]
                        new ODataProperty()
                            Name = "Id",
                            Value = 1,
                        new ODataProperty()
                            Name = "Name",
                            Value = "Tom",

    We’ve already introduced how to write entry in a feed in last section, now we’ll look at how to write entry expanded in another entry.

    ODataMessageWriter writer = new ODataMessageWriter((IODataResponseMessage)message, settings, model);
                IEdmEntitySet entitySet = model.FindDeclaredEntitySet("Customers");
                ODataWriter odataWriter = writer.CreateODataEntryWriter(entitySet);
                ODataEntry entry = new ODataEntry()
                    Properties = new[]
                        new ODataProperty()
                            Name = "Id",
                            Value = 1,
                        new ODataProperty()
                            Name = "Name",
                            Value = "Tom",
                ODataEntry orderEntry = new ODataEntry()
                    Properties = new[]
                        new ODataProperty()
                            Name = "Id",
                            Value = 1,
                        new ODataProperty()
                            Name = "Price",
                            Value = new decimal(3.14)
                odataWriter.WriteStart(new ODataNavigationLink()
                    Name = "Purchases",
                    IsCollection = true
                odataWriter.WriteStart(new ODataFeed());

    The output will contains order entity inside the customer entity.

  • 1.2 Read OData Payload

    The reader API is almost like the writer API, so you can expect the symmetry here.First, we’ll set up the neccessary code that is common to all kind of payload.Class ODataMessageReader is the entrance class to read the OData Payload.To construct an ODataMessageReader instance, you’ll need to provide an IODataResponseMessage, or IODataRequestMessage, depends on if you are reading a response or a request.OData Core library provides no implementation of these two interfaces, because it is different in different scenario.In this tutoria, we’ll still use the InMemoryMessage.cs.

    We’ll still use the model set up in the EDMLIB section.

    IEdmModel model = builder

    Then set up the message to read the payload.

    MemoryStream stream = new MemoryStream();
    InMemoryMessage message = new InMemoryMessage() {Stream = stream};

    Create the settings:

    ODataMessageReaderSettings settings = new ODataMessageReaderSettings();

    Now we are ready to create the ODataMessageReader instance:

    ODataMessageReader reader = new ODataMessageReader((IODataResponseMessage) message, settings);

    We’ll use the code in the first part to write the payload, and in this section use the reader to read the payload. After write the payload, we should set the Position of MemoryStream to zero.

    stream.Position = 0;

    Here is the whole program that use SampleModelBuilder and InMemoryMessage to first write then read metadata payload:

    IEdmModel model = builder
                MemoryStream stream = new MemoryStream();
                InMemoryMessage message = new InMemoryMessage() { Stream = stream };
                ODataMessageWriterSettings writerSettings = new ODataMessageWriterSettings();
                ODataMessageWriter writer = new ODataMessageWriter((IODataResponseMessage)message, writerSettings, model);
                stream.Position = 0;
                ODataMessageReaderSettings settings = new ODataMessageReaderSettings();
                ODataMessageReader reader = new ODataMessageReader((IODataResponseMessage)message, settings);
                IEdmModel modelFromReader = reader.ReadMetadataDocument();

    Now we’ll go through on each kind of payload.

    Read metadata

    Read metadata is simple, just use ReadMetadataDocument method in ODataMessageReader.


    Just like writing metadata, this API only works when reading response message, that means when constructing the ODataMessageReader, you must supply IODataResponseMessage.

    Read service document

    Read service document is through the ReadServiceDocument API.

    ODataMessageReaderSettings readerSettings = new ODataMessageReaderSettings();
                ODataMessageReader reader = new ODataMessageReader((IODataResponseMessage)message, readerSettings, model);
                ODataServiceDocument serviceDocumentFromReader = reader.ReadServiceDocument();

    And as ReadMetadata API, ReadServiceDocument works only when it is reading a response message.

    Besides API ReadServiceDocument, there is another API called ReadServiceDocumentAsync in ODataMessageReader class. It is an async version of ReadServiceDocument, so you can call it in async way.

    ODataServiceDocument serviceDocument = await reader.ReadServiceDocumentAsync();

    Read Feed

    To read a feed, you must create another reader on ODataFeedReader to read the feed. The library is designed to read feed in an streaming way, which means the entry is read one by one.

    Here is how to read a feed.

    ODataMessageReader reader = new ODataMessageReader((IODataResponseMessage)message, readerSettings, model);
                ODataReader feedReader = reader.CreateODataFeedReader(entitySet, entitySet.EntityType());
                while (feedReader.Read())
                    switch (feedReader.State)
                        case ODataReaderState.FeedEnd:
                            ODataFeed feedFromReader = (ODataFeed)feedReader.Item;
                        case ODataReaderState.EntryEnd:
                            ODataEntry entryFromReader = (ODataEntry)feedReader.Item;

    Read Entry

    To read a top level entry, use ODataMessageReader.CreateEntryReader. Other than that, there is no different compared to read feed.

    ODataMessageReader reader = new ODataMessageReader((IODataResponseMessage)message, readerSettings, model);
                ODataReader feedReader = reader.CreateODataEntryReader(entitySet, entitySet.EntityType());
                while (feedReader.Read())
                    switch (feedReader.State)
                        case ODataReaderState.FeedEnd:
                            ODataFeed feedFromReader = (ODataFeed)feedReader.Item;
                        case ODataReaderState.EntryEnd:
                            ODataEntry entryFromReader = (ODataEntry)feedReader.Item;
  • 1.3 Use ODataUriParser

    This post is intended to guide you through the UriParser for OData V4, which is released within ODataLib V6.0 and later.You may have already read the following posts about OData UriParser in ODataLib V5.x:

    UriParser Overview

    The main reference document for UriParser is the URL Conventions specification. The ODataUriParser class is its main implementation in ODataLib.

    The ODataUriParser class has two main functionalities:

    • Parse resource path
    • Parse query options

    We’ve also introduced the new ODataQueryOptionParser class in ODataLib 6.2+, in case you do not have the full resource path and only want to parse the query options only. The ODataQueryOptionParser shares the same API signature for parsing query options, you can find more information below.

    Using ODataUriParser

    The use of ODataUriParser class is easy and straightforward, as we mentioned, we do not support static methods now, we will begin from creating an ODataUriParser instance.

    ODataUriParser has only one constructor:

    public ODataUriParser(IEdmModel model, Uri serviceRoot, Uri fullUri);


    model is the Edm model the UriParser will refer to; serviceRoot is the base Uri for the service, which could be a constant for certain service. Note that serviceRoot must be an absolute Uri; fullUri is the full request Uri including query options. When it is an absolute Uri, it must be based on the serviceRoot, or it can be a relative Uri. In the following demo we will use the model from OData V4 demo service , and create an ODataUriParser instance.

    Uri serviceRoot = new Uri("http://services.odata.org/V4/OData/OData.svc");
    IEdmModel model = EdmxReader.Parse(XmlReader.Create(serviceRoot + "/$metadata"));
    Uri fullUri = new Uri("http://services.odata.org/V4/OData/OData.svc/Products");
    ODataUriParser parser = new ODataUriParser(model, serviceRoot, fullUri);

    Parsing Resource Path

    You can use the following API to parse resource path:

    Uri fullUri = new Uri("http://services.odata.org/V4/OData/OData.svc/Products(1)");
    ODataUriParser parser = new ODataUriParser(model, serviceRoot, fullUri);
    ODataPath path = parser.ParsePath();

    You don’t need to pass in resource path as parameter here, because the constructor has taken the full Uri.

    The ODataPath holds the enumeration of path segments for resource path. All path segments are represented by classes derived from ODataPathSegment.

    In our demo, the resource Path in the full Uri is Products(1), then the result ODataPath would contain two segments: one EntitySetSegment for EntitySet named Products, and the other KeySegment for key with integer value “1” .

    Parsing Query Options

    ODataUriParser supports parsing following query options: $select, $expand, $filter, $orderby, $search, $top, $skip, and $count.

    For the first five, the parsing result is an instance of class XXXClause, which represents the query option as an Abstract Syntax Tree (with semantic information bound). Note that $select and $expand query options are merged together in one SelectExpandClause class. The latter three all have primitive type value, and the parsing result is the corresponding primitive type wrapped by Nullable class.

    For all query option parsing results, the Null value indicates the corresponding query option is not specified in the request URL.

    Here is a demo for parsing the Uri with all kinds of query options (please notice that value of skip would be null as it is not specified in the request Uri) :

    Uri fullUri = new Uri("Products?$select=ID&$expand=ProductDetail" +
        "&$filter=Categories/any(d:d/ID%20gt%201)&$orderby=ID%20desc" +
        "&$top=1&$count=true&$search=tom", UriKind.Relative);
    ODataUriParser parser = new ODataUriParser(model, serviceRoot, fullUri);
    SelectExpandClause expand = 
        parser.ParseSelectAndExpand();              //parse $select, $expand
    FilterClause filter = parser.ParseFilter();     // parse $filter
    OrderByClause orderby = parser.ParseOrderBy();  // parse $orderby
    SearchClause search = parser.ParseSearch();     // parse $search
    long? top = parser.ParseTop();                  // parse $top
    long? skip = parser.ParseSkip();                // parse $skip
    bool? count = parser.ParseCount();              // parse $count

    The data structure for SelectExpandClause, FilterClause, OrdeyByClause have already been presented in the two previous articles mentioned at the top of this post. Here I’d like to talk about the newly introduced SearchClause.

    SearchClause contains tree representation of the $search query. The detailed rule of $search query option can be found here. In general, the search query string can contain search terms combined with logic keywords: AND, OR and NOT.

    All search terms are represented by SearchTermNode, which is derived from SingleValueNode. SearchTermNode has one property named Text, which contains the original word or phrases.

    SearchClause’s Expression property holds the tree structure for $search. If the $search contains single word, the Expression would be set to that SearchTermNode. But when $search is a combination of various term and logic keywords, the Expression would also contains nested BinaryOperatorNode and UnaryOperatorNode.

    For example, if the query option $search has the value “a AND b”, the result expression (syntax tree) would have the following structure:

        Expression = BinaryOperatorNode
                       OperationKind = BinaryOperatorKind.And
                       Left           = SearchTermNode
                                        Text = a
                       Right          = SearchTermNode
                                        Text = b

    Using ODataQueryOption Parser

    There may be some cases that you already know the query context information but does not have the full request Uri. The ODataUriParser does not seems to be available as it will always require the full Uri, then the user would have to fake one.

    In ODataLib 6.2 we shipped a new Uri parser that targets at query options only, it requires the model and type information be provided through its constructor, then it could be used for query options parsing as same as ODataUriParser.

    The constructor looks like this:

    public ODataQueryOptionParser(IEdmModel model, IEdmType targetEdmType, IEdmNavigationSource targetNavigationSource, IDictionary<string, string> queryOptions);

    Parameters (here the target object indicates what resource path was addressed, see spec):

    model is the model the UriParser will refer to; targetEdmType is the type query options apply to, it is the type of target object; targetNavigationSource is the EntitySet or Singleton where the target comes from, it is usually the NavigationSource of the target object; queryOptions is the dictionary containing the key-value pairs for query options.

    Here is the demo for its usage, it is almost the same as the ODataUriParser:

    Dictionary<string, string> options = new Dictionary<string, string>()
        {"$select"  , "ID"                          },
        {"$expand"  , "ProductDetail"               },
        {"$filter"  , "Categories/any(d:d/ID gt 1)" },
        {"$orderby" , "ID desc"                     },
        {"$top"     , "1"                           },
        {"$count"   , "true"                        },
        {"$search"  , "tom"                         },
    IEdmType type = model.FindDeclaredType("ODataDemo.Product");
    IEdmNavigationSource source = model.FindDeclaredEntitySet("Products");
    ODataQueryOptionParser parser 
        = new ODataQueryOptionParser(model, type, source, options);
    SelectExpandClause selectExpand = 
        parser.ParseSelectAndExpand();              //parse $select, $expand
    FilterClause filter = parser.ParseFilter();     // parse $filter
    OrderByClause orderby = parser.ParseOrderBy();  // parse $orderby
    SearchClause search = parser.ParseSearch();     // parse $search
    long? top = parser.ParseTop();                  // parse $top
    long? skip = parser.ParseSkip();                // parse $skip (null)
    bool? count = parser.ParseCount();              // parse $count

Continue reading “ODataLib”

Oracle Data Provider for .NET / ODP.NET connection strings

Using TNS

Data Source=TORCL;User Id=myUsername;Password=myPassword;


Data Source=localhost:1521/orcl; User Id=scott; Password=scott;

Using integrated security

Data Source=TORCL;Integrated Security=SSPI;

Using ODP.NET without tnsnames.ora

User Id=myUsername;Password=myPassword;

Using the Easy Connect Naming Method (aka EZ Connect)

The easy connect naming method enables clients to connect to a database without any configuration.

Data Source=username/[email protected]//myserver:1521/my.service.com;

Port 1521 is used if no port number is specified in the connection string.

Make sure that EZCONNECT is enabled in the sqlnet.ora file. NAMES.DIRECTORY_PATH= (TNSNAMES, EZCONNECT)

‘//’ in data source is optional and is there to enable URL style hostname values

Easy Connect Naming Method to connect to an Instance

This one does not specify a service or a port.

Data Source=username/[email protected]//instancename;

Easy Connect Naming Method to connect to a dedicated server instance

This one does not specify a service or a port.

Data Source=username/[email protected]/myservice:dedicated/instancename;

Other server options: SHARED, POOLED (to use instead of DEDICATED). Dedicated is the default.

Specifying Pooling parameters

By default, connection pooling is enabled. This one controls the pooling mechanisms. The connection pooling service creates connection pools by using the ConnectionString property to uniquely identify a pool.

Data Source=myOracle;User Id=myUsername;Password=myPassword;Min Pool Size=10;
Connection Lifetime=120;Connection Timeout=60;Incr Pool Size=5;Decr Pool Size=2;

The first connection opened creates the connection pool. The service initially creates the number of connections defined by the Min Pool Size parameter.

The Incr Pool Size attribute defines the number of new connections to be created by the connection pooling service when more connections are needed.

When a connection is closed, the connection pooling service determines whether the connection lifetime has exceeded the value of the Connection Lifetime attribute. If so, the connection is closed; otherwise, the connection goes back to the connection pool.

The connection pooling service closes unused connections every 3 minutes. The Decr Pool Size attribute specifies the maximum number of connections that can be closed every 3 minutes.

Restricting Pool size

Use this one if you want to restrict the size of the pool.

Data Source=myOracle;User Id=myUsername;Password=myPassword;Max Pool Size=40;
Connection Timeout=60;

The Max Pool Size attribute sets the maximum number of connections for the connection pool. If a new connection is requested, but no connections are available and the limit for Max Pool Size has been reached the connection pooling service waits for the time defined by the Connection Timeout attribute. If the Connection Timeout time has been reached, and there are still no connections available in the pool, the connection pooling service raises an exception indicating that the request has timed-out.

Disable Pooling

Data Source=myOracle;User Id=myUsername;Password=myPassword;Pooling=False;

Using Windows user authentication

Oracle can open a connection using Windows user login credentials to authenticate database users.

Data Source=myOracle;User Id=/;

If the Password attribute is provided, it is ignored.

Operating System Authentication is not supported in a .NET stored procedure.

Privileged Connections

Oracle allows database administrators to connect to Oracle Database with either SYSDBA or SYSOPER privileges.

Data Source=myOracle;User Id=myUsername;Password=myPassword;DBA Privilege=SYSDBA;

SYSOPER is also valid for the DBA Privilege attribute.

Runtime Connection Load Balancing

Optimizes connection pooling for RAC database by balancing work requests across RAC instances.

Data Source=myOracle;User Id=myUsername;Password=myPassword;Load Balancing=True;

This feature can only be used against a RAC database and only if pooling is enabled (default).



 正则表达式中的反斜杠字符 (\) 指示其后跟的字符是特殊字符(如下表所示),或应按原义解释该字符。 有关详细信息,请参阅正则表达式中的字符转义。
转义字符 描述 模式 匹配
\a 与报警 (bell) 符 \u0007 匹配。 \a “Error!”+“\u0007”中的“\u0007”
\b 在字符类中,与退格键 \u0008 匹配。 [\b]{3,} “\b\b\b\b”中的“\b\b\b\b”
\t 与制表符 \u0009 匹配。 (\w+)\t “item1\titem2\t”中的“item1\t”和“item2\t”
\r 与回车符 \u000D 匹配。 (\r 与换行符 \n 不是等效的。) \r\n(\w+) “\r\nThese are\ntwo lines.”中的“\r\nThese”
\v 与垂直制表符 \u000B 匹配。 [\v]{2,} “\v\v\v”中的“\v\v\v”
\f 与换页符 \u000C 匹配。 [\f]{2,} “\f\f\f”中的“\f\f\f”
\n 与换行符 \u000A 匹配。 \r\n(\w+) “\r\nThese are\ntwo lines.”中的“\r\nThese”
\e 与转义符 \u001B 匹配。 \e “\x001B”中的“\x001B”
\nnn 使用八进制表示形式指定字符(nnn 由二位或三位数字组成)。 \w\040\w “a bc d”中的

“a b”和“c d”

\xnn 使用十六进制表示形式指定字符(nn 恰好由两位数字组成)。 \w\x20\w “a bc d”中的

“a b”和“c d”

\c X

\c x

匹配 X 或 x 指定的 ASCII 控件字符,其中 X 或 x 是控件字符的字母。 \cC “\x0003”中的“\x0003”(Ctrl-C)
\unnnn 使用十六进制表示形式匹配 Unicode 字符(由 nnnn 正确表示的四位数)。 \w\u0020\w “a bc d”中的

“a b”和“c d”

\ 在后面带有不识别为本主题的此表和其他表中的转义符的字符时,与该字符匹配。 例如,\* 与 \x2A 相同,而 \. 与 \x2E 相同。 这允许正则表达式引擎区分语言元素(如 * 或 ?)和字符(用 \* 或 \? 表示)。 \d+[\+-x\*]\d+\d+[\+-x\*\d+ “(2+2) * 3*9”中的“2+2”和“3*9”


字符类与一组字符中的任何一个字符匹配。 字符类包括下表中列出的语言元素。 有关详细信息,请参阅正则表达式中的字符类。
字符类 描述 模式 匹配
[ character_group ] 匹配 character_group 中的任何单个字符。 默认情况下,匹配区分大小写。 [ae] “gray”中的“a”


[^ character_group ] 求反:与不在 character_group 中的任何单个字符匹配。 默认情况下,character_group 中的字符区分大小写。 [^aei] “reign”中的“r”、“g”和“n”
[ 第一个 – 最后一个 ] 字符范围:与从第一个到最后一个的范围中的任何单个字符匹配。 [A-Z] “AB123”中的“A”和“B”
. 通配符:与除 \n 之外的任何单个字符匹配。

若要匹配文本句点字符(. 或 \u002E),你必须在该字符前面加上转义符 (\.)。

a.e “nave”中的“ave”


\p{ 名称 } 与 name 指定的 Unicode 通用类别或命名块中的任何单个字符匹配。 \p{Lu}


“City Lights”中的“C”和“L”


\P{ 名称 } 与不在 name 指定的 Unicode 通用类别或命名块中的任何单个字符匹配。 \P{Lu}




\w 与任何单词字符匹配。 \w “ID A1.3”中的“I”、“D”、“A”、“1”和“3”
\W 与任何非单词字符匹配。 \W “ID A1.3”中的“ ”、“.”
\s 与任何空白字符匹配。 \w\s “ID A1.3”中的“D”
\S 与任何非空白字符匹配。 \s\S ” _” in “int __ctr”
\d 与任何十进制数字匹配。 \d “4 = IV”中的“4”
\D 匹配不是十进制数的任意字符。 \D “4 = IV”中的“ ”、“=”、“ ”、“I”和“V”


定位点或原子零宽度断言会使匹配成功或失败,具体取决于字符串中的当前位置,但它们不会使引擎在字符串中前进或使用字符。 下表中列出的元字符是定位点。 有关详细信息,请参阅正则表达式中的定位点。
断言 描述 模式 匹配
^ 匹配必须从字符串或一行的开头开始。 ^\d{3} “901”


$ 匹配必须出现在字符串的末尾或出现在行或字符串末尾的 \n 之前。 -\d{3}$ “-333”


\A 匹配必须出现在字符串的开头。 \A\d{3} “901”


\Z 匹配必须出现在字符串的末尾或出现在字符串末尾的 \n 之前。 -\d{3}\Z “-333”


\z 匹配必须出现在字符串的末尾。 -\d{3}\z “-333”


\G 匹配必须出现在上一个匹配结束的地方。 \G\(\d\) “(1)(3)(5)[7](9)”中的“(1)”、“(3)”、“(5)”
\b 匹配必须出现在 \w(字母数字)和 \W(非字母数字)字符之间的边界上。 \b\w+\s\w+\b “them theme them them”中的“them theme”、“them them”
\B 匹配不得出现在 \b 边界上。 \Bend\w*\b “end sends endure lender”中的“ends”和“ender”


分组构造描述了正则表达式的子表达式,通常用于捕获输入字符串的子字符串。 分组构造包括下表中列出的语言元素。 有关详细信息,请参阅正则表达式中的分组构造。
分组构造 描述 模式 匹配
( 子表达式 ) 捕获匹配的子表达式并将其分配到一个从 1 开始的序号中。 (\w)\1 “deep”中的“ee”
(?< 名称 > 子表达式 ) 将匹配的子表达式捕获到一个命名组中。 (?<double>\w)\k<double> “deep”中的“ee”
(?< 名称 1 – 名称 2 > 子表达式 ) 定义平衡组定义。 有关详细信息,请参阅正则表达式中的分组构造中的“平衡组定义”部分。 (((?’Open’\()[^\(\)]*)+((?’Close-Open’\))[^\(\)]*)+)*(?(Open)(?!))$ “3+2^((1-3)*(3-1))”中的“((1-3)*(3-1))”
(?: 子表达式 ) 定义非捕获组。 Write(?:Line)? “Console.WriteLine()”中的“WriteLine”


(?imnsx-imnsx: 子表达式 ) 应用或禁用子表达式中指定的选项。 有关详细信息,请参阅正则表达式选项。 A\d{2}(?i:\w+)\b “A12xl A12XL a12xl”中的“A12xl”和“A12XL”
(?= 子表达式 ) 零宽度正预测先行断言。 \w+(?=\.) “He is. The dog ran. The sun is out.”中的“is”、“ran”和“out”
(?! 子表达式 ) 零宽度负预测先行断言。 \b(?!un)\w+\b “unsure sure unity used”中的“sure”和“used”
(?<= 子表达式 ) 零宽度正回顾后发断言。 (?<=19)\d{2}\b “1851 1999 1950 1905 2003”中的“99”、“50”和“05”
(?<! 子表达式 ) 零宽度负回顾后发断言。 (?<!19)\d{2}\b “1851 1999 1950 1905 2003”中的“51”和“03”
(?> 子表达式 ) 非回溯(也称为“贪婪”)子表达式。 [13579](?>A+B+) “1ABB 3ABBC 5AB 5AC”中的“1ABB”、“3ABB”和“5AB”


限定符指定在输入字符串中必须存在上一个元素(可以是字符、组或字符类)的多少个实例才能出现匹配项。 限定符包括下表中列出的语言元素。 有关详细信息,请参阅正则表达式中的限定符。
限定符 描述 模式 匹配
* 匹配上一个元素零次或多次。 \d*\. \d “.0”,“19.9”和“219.9”
+ 匹配上一个元素一次或多次。 “be+” “been”中的“bee”,“bent”中的“be”
? 匹配上一个元素零次或一次。 “rai? n” “ran”和“rain”
{ n } 匹配上一个元素恰好 n 次。 “,\d{3}” “1,043.6”中的“,043”,“9,876,543,210”中的“,876”、“,543”和“,210”
{ n ,} 匹配上一个元素至少 n 次。 “\d{2,}” “166”,“29”和“1930”
{ n , m } 匹配上一个元素至少 n 次,但不多于 m 次。 “\d{3,5}” “166”、“17668”


*? 匹配上一个元素零次或多次,但次数尽可能少。 \d*? \. \d “.0”,“19.9”和“219.9”
+? 匹配上一个元素一次或多次,但次数尽可能少。 “be+?” “been”中的“be”,“bent”中的“be”
?? 匹配上一个元素零次或一次,但次数尽可能少。 “rai?? n” “ran”和“rain”
{ n }? 匹配前面的元素恰好 n 次。 “,\d{3}?” “1,043.6”中的“,043”,“9,876,543,210”中的“,876”、“,543”和“,210”
{ n ,}? 匹配上一个元素至少 n 次,但次数尽可能少。 “\d{2,}?” “166”,“29”和“1930”
{ n , m }? 匹配上一个元素的次数介于 n 和 m 之间,但次数尽可能少。 “\d{3,5}?” “166”、“17668”



反向引用允许在同一正则表达式中随后标识以前匹配的子表达式。 下表列出了 .NET Framework 的正则表达式支持的反向引用构造。 有关详细信息,请参阅正则表达式中的反向引用构造。
反向引用构造 描述 模式 匹配
\ number 后向引用。 匹配编号子表达式的值。 (\w)\1 “seek”中的“ee”
\k< 名称 > 命名后向引用。 匹配命名表达式的值。 (?<char>\w)\k<char> “seek”中的“ee”


替换构造用于修改正则表达式以启用 either/or 匹配。 这些构造包括下表中列出的语言元素。 有关详细信息,请参阅正则表达式中的备用构造。
替换构造 描述 模式 匹配
| 匹配以竖线 (|) 字符分隔的任何一个元素。 th(e|is|at) “this is the day.”中的“the”和“this”
(?( expression ) yesno ) 如果正则表达式模式由 expression 匹配指定,则匹配 yes;否则,匹配可选 no 部分。expression 被解释为零宽度断言。 (?(A)A\d{2}\b|\b\d{3}\b) “A10 C103 910”中的“A10”和“910”
(?( name ) yes no) 如果 name(已命名或已编号的捕获组)具有匹配,则匹配 yes;否则,匹配可选 no。 (?<quoted>”)?(?(quoted).+?”|\S+\s) “Dogs.jpg “Yiska playing.jpg””中的 Dogs.jpg 和 “Yiska playing.jpg”


替换是替换模式中支持的正则表达式语言元素。 有关详细信息,请参阅正则表达式中的替代。 下表中列出的元字符是原子零宽度断言。
字符 描述 模式 替换模式 输入字符串 结果字符串
$ number 替换按组 number 匹配的子字符串。 \b(\w+)(\s)(\w+)\b $3$2$1 “one two” “two one”
${ 名称 } 替换按命名组 name 匹配的子字符串。 \b(?<word1>\w+)(\s)(?<word2>\w+)\b ${word2} ${word1} “one two” “two one”
$$ 替换字符“$”。 \b(\d+)\s?USD $$$1 “103 USD” “$103”
$& 替换整个匹配项的一个副本。 \$? \d*\.? \d+ **$&** “$1.30” “**$1.30**”
$` 替换匹配前的输入字符串的所有文本。 B+ $` “AABBCC” “AAAACC”
$’ 替换匹配后的输入字符串的所有文本。 B+ $’ “AABBCC” “AACCCC”
$+ 替换最后捕获的组。 B+(C+) $+ “AABBCCDD” AACCDD
$_ 替换整个输入字符串。 B+ $_ “AABBCC” “AAAABBCCCC”


可以指定控制正则表达式引擎如何解释正则表达式模式的选项。 其中的许多选项可以指定为内联(在正则表达式模式中)或指定为一个或多个 RegexOptions 常量。 本快速参考仅列出内联选项。 有关内联和RegexOptions 选项的详细信息,请参阅文章正则表达式选项。


  • 通过使用杂项构造(?imnsx-imnsx),可以用选项或选项组前的减号 (-) 关闭这些选项。 例如,(?i-mn) 启用不区分大小写的匹配 (i),关闭多行模式 (m) 并关闭未命名的组捕获 (n)。 该选项自定义选项的点开始应用于此正则表达式,且持续有效直到模式结束或者到另一构造反转此选项的点。
  • 通过使用分组构造(?imnsx-imnsx:子表达式)(只定义指定组的选项)。

.NET Framework 正则表达式引擎支持以下内联选项。

选项 描述 模式 匹配
i 使用不区分大小写的匹配。 \b(?i)a(?-i)a\w+\b “aardvark”, “aaaAuto” in “aardvark AAAuto aaaAuto Adam breakfast”
m 使用多行模式。 ^ 和 $ 匹配行的开头和结尾,但不匹配字符串的开头和结尾。 有关示例,请参阅正则表达式选项中的“多行模式”部分。
n 不捕获未命名的组。 有关示例,请参阅正则表达式选项中的“仅显式捕获”部分。
s 使用单行模式。 有关示例,请参阅正则表达式选项中的“单行模式”部分。
x 忽略正则表达式模式中的非转义空白。 \b(?x) \d+ \s \w+ “1 aardvark 2 cats IV centurions”中的“1 aardvark”、“2 cats”


其他构造可修改某个正则表达式模式或提供有关该模式的信息。 下表列出了 .NET Framework 支持的其他构造。 有关详细信息,请参阅正则表达式中的其他构造。

构造 定义 示例
(?imnsx-imnsx) 在模式中间对诸如不区分大小写这样的选项进行设置或禁用。 有关详细信息,请参阅正则表达式选项。 \bA(?i)b\w+\b 匹配“ABA Able Act”中的“ABA”和“Able”
(?# comment ) 内联注释。 该注释在第一个右括号处终止。 \bA(?#Matches words starting with A)\w+\b
# [至行尾] X 模式注释。 该注释以非转义的 # 开头,并继续到行的结尾。 (?x)\bA\w+\b#Matches words starting with A


转载自CNBLOGS, 原作者 慕容小匹夫


其实小匹夫在U3D的开发中一直对U3D的跨平台能力很好奇。到底是什么原理使得U3D可以跨平台呢?后来发现了Mono的作用,并进一步了解到了 CIL的存在。所以,作为一个对Unity3D跨平台能力感兴趣的U3D程序猿,小匹夫如何能不关注CIL这个话题呢?那么下面各位看官就拾起语文老师教 导我们的作文口诀(WhyWhatHow),和小匹夫一起走进CIL的世界吧~






那么来到U3D为何能跨平台,简而言之,其实现原理在于使用了叫CIL(Common Intermediate Language通用中间语言,也叫做MSIL微软中间语言)的一种代码指令集,CIL可以在任何支持CLI(Common Language Infrastructure,通用语言基础结构)的环境中运行,就像.NET是微软对这一标准的实现,Mono则是对CLI的又一实现。由于CIL能运 行在所有支持CLI的环境中,例如刚刚提到的.NET运行时以及Mono运行时,也就是说和具体的平台或者CPU无关。这样就无需根据平台的不同而部署不 同的内容了。所以到这里,各位也应该恍然大了。代码的编译只需要分为两部分就好了嘛:

  1. 从代码本身到CIL的编译(其实之后CIL还会被编译成一种位元码,生成一个CLI assembly)
  2. 运行时从CIL(其实是CLI assembly,不过为了直观理解,不必纠结这种细节)到本地指令的即时编译(这就引出了为何U3D官方没有提供热更新的原因:在iOS平台中Mono无法使用JIT引擎,而是以Full AOT模式运行的,所以此处说的额即时编译不包括IOS





class Class1
    public static void Main(string[] args)


.class private auto ansi beforefieldinit Class1
       extends [mscorlib]System.Object
  .method public hidebysig static void  Main(string[] args) cil managed
    // 代码大小       13 (0xd)
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldstr      "hi"
    IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_000b:  nop
    IL_000c:  ret
  } // end of method Class1::Main

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
    // 代码大小       7 (0x7)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  ret
  } // end of method Class1::.ctor

} // end of class Class1


  1. 以“.”一个点号开头的,例如上面这份代码中的:.class、.method 。我们称之为CIL指令(directive),用于描述.NET程序集总体结构的标记。为啥需要它呢?因为你总得告诉编译器你处理的是啥吧。
  2. 貌似CIL代码中还看到了private、public这样的身影。姑且称之为CIL特性(attribute)。它的作用也很好理解,通过CIL指令并不能完全说明.NET成员和类,针对CIL指令进行补充说明成员或者类的特性的。市面上常见的还有:extends,implements等等。
  3. 每一行CIL代码基本都有的,对,那就是CIL操作码咯。小匹夫从网上找了一份汉化的操作码表放在附录部分,当然英文版的你的vs就有。




主要操作 操作数范围/条件 操作数类型 操作数
缩写 全称 含义 缩写 全称 含义 缩写 全称 含义 缩写 全称 含义
ld load 将操作数压到堆栈当中,相当于:
push ax
arg argument 参数 ? ? 操作数中的数值 .0 ? 第零个参数
.1 ? 第一个参数
.2 ? 第二个参数
.3 ? 第三个参数
.s xx (short) 参数xx
a address 操作数的地址 只有 .s xx,参见ldarg.s
loc local 局部变量 参见ldarg
fld field 字段(类的全局变量) 参见ldarg xx ? xx字段,eg:
ldfld xx
c const 常量 .i4 int 4 bytes C#里面的int,其他的类型例如short需要通过conv转换 .m1 minus 1 -1
.0 ? 0
.1 ? 1
.8 8
.s (short) 后面跟一个字节以内的整型数值(有符号的)
? ? 后面跟四个字节的整型数值
.i8 int 8 bytes C#里面的long ? ? 后面跟八个字节的整型数值
.r4 real 4 bytes C#里面的float ? ? 后面跟四个字节的浮点数值
.r8 real 8 bytes C#里面的double ? ? 后面跟八个字节的浮点数值
null null 空值(也就是0) ? ? ? ? ? ?
st store 计算堆栈的顶部弹出当前值,相当于:
pop ax
conv convert 数值类型转换,仅仅用纯粹的数值类型间的转换,例如int/float等 ? ? ? .i1 int 1 bytes C#里面的sbyte ? ? ?
.i2 int 2 bytes C#里面的short
.i4 int 4 bytes C#里面的int
.i8 int 8 bytes C#里面的long
.r4 real 4 bytes C#里面的float
.r8 real 8 bytes C#里面的double
.u4 uint 4 bytes C#里面的uint
.u8 uint 8 bytes C#里面的ulong
b/br branch 条件和无条件跳转,相当于:
jmp/jxx label_jump
br ? ? 无条件跳转 ? ? ? ? ? 后面跟四个字节的偏移量(有符号)
.s (short) 后面跟一个字节的偏移量(有符号)
false false 值为零的时候跳转 ? ? ? 参见br
true true 值不为零的时候跳转 ? ? ?
b eq equal to 相等 ? ? ?
ne not equal to 不相等 un unsigned or unordered 无氟好的(对于整数)或者无序的(对于浮点)
gt greater than 大于
lt less than 小于
ge greater than or equal to 大于等于
le less than or equal to 小于等于
call call 调用 ? ? ? ? ? (非虚函数) ?
? ? ? virt virtual 虚函数





add eax,-2


当然,CIL之所以是基于堆栈而非CPU的另一个原因是相比较于cpu的寄存器,操作堆栈实在太简单了。回到刚才小匹夫说的大学时候曾经学过的单片 机那门课程上,当时记得各种寄存器,各种标志位,各种。。。,而堆栈只需要简单的压栈和弹出,因此对于虚拟机的实现来说是再合适不过了。所以想要更具体的 了解CIL基于堆栈这一点,各位可以去看一下堆栈方面的内容。这里小匹夫就不拓展了。





  1. 静态方法:ldarg.0么有被占用,所以参数从ldarg.0开始。
  2. 实例方法:ldarg.0是被this占用的,也就是说实际上的参数是从ldarg.1开始的。

举个例子:假设你有一个类Murong中有一个静态方法Add(int32 a, int32 b),实现的内容就如同它的名字一样使两个数相加,所以需要2个参数。和一个实例方法TellName(string name),这个方法会告诉你传入的名字。

class  Murong
    public void TellName(string name)

    public static int Add(int a, int b)
       return a + b;



.method public hidebysig static int32  Add(int32 a,
                                           int32 b) cil managed
  // 代码大小       9 (0x9)
  .maxstack  2
  .locals init ([0] int32 CS$1$0000)   //初始化局部变量列表。因为我们只返回了一个int型。所以这里声明了一个int32类型。索引为0
  IL_0000:  nop
  IL_0001:  ldarg.0     //将索引为 0 的参数加载到计算堆栈上。
  IL_0002:  ldarg.1     //将索引为 1 的参数加载到计算堆栈上。
  IL_0003:  add          //计算
  IL_0004:  stloc.0      //从计算堆栈的顶部弹出当前值并将其存储到索引 0 处的局部变量列表中。
  IL_0005:  br.s       IL_0007
  IL_0007:  ldloc.0     //将索引 0 处的局部变量加载到计算堆栈上。
  IL_0008:  ret           //返回该值
} // end of method Murong::Add


Murong.Add(1, 2);


  IL_0001:  ldc.i4.1 //将整数1压入栈中
  IL_0002:  ldc.i4.2 //将整数2压入栈中
  IL_0003:  call       int32 Murong::Add(int32,
                                         int32)  //调用静态方法




.method public hidebysig instance void  TellName(string name) cil managed
  // 代码大小       9 (0x9)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldarg.1     //看到和静态方法的区别了吗?
  IL_0002:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0007:  nop
  IL_0008:  ret
} // end of method Murong::TellName



Murong murong = new Murong();


.locals init ([0] class Murong murong)   //因为C#代码中定义了一个Murong类型的变量,所以局部变量列表的索引0为该类型的引用。
IL_0009:  newobj     instance void Murong::.ctor() //相比上面的静态方法的调用,此处new一个新对象,出现了instance方法。
IL_000e:  stloc.0
IL_000f:  ldloc.0
IL_0010:  ldstr      "chenjiadong" //小匹夫的名字入栈
IL_0015:  callvirt   instance void Murong::TellName(string) //实例方法的调用也有instance





Q:上面的Why部分,咱们知道了U3D能跨平台是因为存在着一个能通吃的中间语言CIL,这也是所谓跨平台的前提,但是为啥CIL能通吃各大平台 呢?当然可以说CIL基于堆栈,跟你CPU怎么架构的没啥关系,但是感觉过于理论化、学术化,那还有没有通俗化、工程化的说法呢?

A:原因就是前面小匹夫提到过的,.Net运行时和Mono运行时。也就是说CIL语言其实是运行在虚拟机中的,具体到咱们的U3D也就是mono 的运行时了,换言之mono运行的其实CIL语言,CIL也并非真正的在本地运行,而是在mono运行时中运行的,运行在本地的是被编译后生成的原生代 码。当然看官博的文章,他们似乎也在开发自己的“mono”,也就是被称为脚本的未来的IL2Cpp,这种类似运行时的功能是将IL再编译成c++,再由 c++编译成原生代码,据说效率提升很可观,小匹夫也是蛮期待的。






mcs Test.cs


好像没见有叫.IL的文件生成啊?反而好像多了一个.exe文件?可是没听说Mac能运行exe文件呀?可为啥又生成了.exe呢?各位看官可能要 说,小匹夫你是不是拿windows截图P的啊?嘿嘿,小匹夫可不敢。辣么真相其实就是这个exe并不是让Mac来运行的,而是留给mono运行时来运行 的,换言之这个文件的可执行代码形式是CIL的位元码形态。到此,我们完成了从C#到CIL的过程。接下来就让我们运行下刚刚的成果好啦。

mono Test.exe


从CIL到Native Code


为啥呢?为啥C#写的代码能跑在MAC上呢?这就不得不提从CIL如何到本机原生代码的过程了。Mono提供了两种编译方式,就是我们经常能看到 的:JIT(Just-in-Time compilation,即时编译)和AOT(Ahead-of-Time,提前编译或静态编译)。这两种方式都是将CIL进一步编译成平台的原生代码。 这也是实现跨平台的最后一步。下面就分头介绍一下。


从名字就能看的出来,即时编译,或者称之为动态编译,是在程序执行时才编译代码,解释一条语句执行一条语句,即将一条中间的托管的语句翻译成一条机 器语句,然后执行这条机器语句。但同时也会将编译过的代码进行缓存,而不是每一次都进行编译。所以可以说它是静态编译和解释器的结合体。不过你想想机器既 要处理代码的逻辑,同时还要进行编译的工作,所以其运行时的效率肯定是受到影响的。因此,Mono会有一部分代码通过AOT静态编译,以降低在程序运行时 JIT动态编译在效率上的问题。




mono --aot Test.exe


从图中可以看到JIT time: 39 ms,也就是说Mono的AOT模式其实会使用到JIT,同时我们看到了生成了一个适应小匹夫的MAC的动态库Test.exe.dylib,而在Linux生成就是.so(共享库)。


mono --aot=metadata-only Test.exe



  1. 收集要被编译的方法
  2. 使用JIT进行编译
  3. 发射(Emitting)经JIT编译过的代码和其他信息
  4. 直接生成文件或者调用本地汇编器或连接器进行处理之后生成文件。(例如上图中使用了小匹夫本地的gcc)

Full AOT

当然上文也说了,IOS平台是禁止使用JIT的,可看样子Mono的AOT模式仍然会保留一部分代码会在程序运行时动态编译。所以为了破解这个问 题,Mono提供了一个被称为Full AOT的模式。即预先对程序集中的所有CIL代码进行AOT编译生成一个本地代码映像,然后在运行时直接加载这个映像而不再使用JIT引擎。目前由于技术 或实现上的原因在使用Full AOT时有一些限制,不过这里不再多说了。以后也还会更细的分析下AOT。



  1. CIL是CLI标准定义的一种可读性较低的语言。
  2. 以.NET或mono等实现CLI标准的运行环境为目标的语言要先编译成CIL,之后CIL会被编译,并且以位元码的形式存在(源代码—>中间语言的过程)。
  3. 这种位元码运行在虚拟机中(.net mono的运行时)。
  4. 这种位元码可以被进一步编译成不同平台的原生代码(中间语言—>原生代码的过程)。
  5. 面向对象
  6. 基于堆栈


名称 说明
Add 将两个值相加并将结果推送到计算堆栈上。
Add.Ovf 将两个整数相加,执行溢出检查,并且将结果推送到计算堆栈上。
Add.Ovf.Un 将两个无符号整数值相加,执行溢出检查,并且将结果推送到计算堆栈上。
And 计算两个值的按位“与”并将结果推送到计算堆栈上。
Arglist 返回指向当前方法的参数列表的非托管指针。
Beq 如果两个值相等,则将控制转移到目标指令。
Beq.S 如果两个值相等,则将控制转移到目标指令(短格式)。
Bge 如果第一个值大于或等于第二个值,则将控制转移到目标指令。
Bge.S 如果第一个值大于或等于第二个值,则将控制转移到目标指令(短格式)。
Bge.Un 当比较无符号整数值或不可排序的浮点型值时,如果第一个值大于第二个值,则将控制转移到目标指令。
Bge.Un.S 当比较无符号整数值或不可排序的浮点型值时,如果第一个值大于第二个值,则将控制转移到目标指令(短格式)。
Bgt 如果第一个值大于第二个值,则将控制转移到目标指令。
Bgt.S 如果第一个值大于第二个值,则将控制转移到目标指令(短格式)。
Bgt.Un 当比较无符号整数值或不可排序的浮点型值时,如果第一个值大于第二个值,则将控制转移到目标指令。
Bgt.Un.S 当比较无符号整数值或不可排序的浮点型值时,如果第一个值大于第二个值,则将控制转移到目标指令(短格式)。
Ble 如果第一个值小于或等于第二个值,则将控制转移到目标指令。
Ble.S 如果第一个值小于或等于第二个值,则将控制转移到目标指令(短格式)。
Ble.Un 当比较无符号整数值或不可排序的浮点型值时,如果第一个值小于或等于第二个值,则将控制转移到目标指令。
Ble.Un.S 当比较无符号整数值或不可排序的浮点值时,如果第一个值小于或等于第二个值,则将控制权转移到目标指令(短格式)。
Blt 如果第一个值小于第二个值,则将控制转移到目标指令。
Blt.S 如果第一个值小于第二个值,则将控制转移到目标指令(短格式)。
Blt.Un 当比较无符号整数值或不可排序的浮点型值时,如果第一个值小于第二个值,则将控制转移到目标指令。
Blt.Un.S 当比较无符号整数值或不可排序的浮点型值时,如果第一个值小于第二个值,则将控制转移到目标指令(短格式)。
Bne.Un 当两个无符号整数值或不可排序的浮点型值不相等时,将控制转移到目标指令。
Bne.Un.S 当两个无符号整数值或不可排序的浮点型值不相等时,将控制转移到目标指令(短格式)。
Box 将值类转换为对象引用(O 类型)。
Br 无条件地将控制转移到目标指令。
Br.S 无条件地将控制转移到目标指令(短格式)。
Break 向公共语言结构 (CLI) 发出信号以通知调试器已撞上了一个断点。
Brfalse 如果 value 为 false、空引用(Visual Basic 中的 Nothing)或零,则将控制转移到目标指令。
Brfalse.S 如果 value 为 false、空引用或零,则将控制转移到目标指令。
Brtrue 如果 value 为 true、非空或非零,则将控制转移到目标指令。
Brtrue.S 如果 value 为 true、非空或非零,则将控制转移到目标指令(短格式)。
Call 调用由传递的方法说明符指示的方法。
Calli 通过调用约定描述的参数调用在计算堆栈上指示的方法(作为指向入口点的指针)。
Callvirt 对对象调用后期绑定方法,并且将返回值推送到计算堆栈上。
Castclass 尝试将引用传递的对象转换为指定的类。
Ceq 比较两个值。如果这两个值相等,则将整数值 1 (int32) 推送到计算堆栈上;否则,将 0 (int32) 推送到计算堆栈上。
Cgt 比较两个值。如果第一个值大于第二个值,则将整数值 1 (int32) 推送到计算堆栈上;反之,将 0 (int32) 推送到计算堆栈上。
Cgt.Un 比较两个无符号的或不可排序的值。如果第一个值大于第二个值,则将整数值 1 (int32) 推送到计算堆栈上;反之,将 0 (int32) 推送到计算堆栈上。
Ckfinite 如果值不是有限数,则引发 ArithmeticException。
Clt 比较两个值。如果第一个值小于第二个值,则将整数值 1 (int32) 推送到计算堆栈上;反之,将 0 (int32) 推送到计算堆栈上。
Clt.Un 比较无符号的或不可排序的值 value1 和 value2。如果 value1 小于 value2,则将整数值 1 (int32 ) 推送到计算堆栈上;反之,将 0 ( int32 ) 推送到计算堆栈上。
Constrained 约束要对其进行虚方法调用的类型。
Conv.I 将位于计算堆栈顶部的值转换为 native int。
Conv.I1 将位于计算堆栈顶部的值转换为 int8,然后将其扩展(填充)为 int32。
Conv.I2 将位于计算堆栈顶部的值转换为 int16,然后将其扩展(填充)为 int32。
Conv.I4 将位于计算堆栈顶部的值转换为 int32。
Conv.I8 将位于计算堆栈顶部的值转换为 int64。
Conv.Ovf.I 将位于计算堆栈顶部的有符号值转换为有符号 native int,并在溢出时引发 OverflowException。
Conv.Ovf.I.Un 将位于计算堆栈顶部的无符号值转换为有符号 native int,并在溢出时引发 OverflowException。
Conv.Ovf.I1 将位于计算堆栈顶部的有符号值转换为有符号 int8 并将其扩展为 int32,并在溢出时引发 OverflowException。
Conv.Ovf.I1.Un 将位于计算堆栈顶部的无符号值转换为有符号 int8 并将其扩展为 int32,并在溢出时引发 OverflowException。
Conv.Ovf.I2 将位于计算堆栈顶部的有符号值转换为有符号 int16 并将其扩展为 int32,并在溢出时引发 OverflowException。
Conv.Ovf.I2.Un 将位于计算堆栈顶部的无符号值转换为有符号 int16 并将其扩展为 int32,并在溢出时引发 OverflowException。
Conv.Ovf.I4 将位于计算堆栈顶部的有符号值转换为有符号 int32,并在溢出时引发 OverflowException。
Conv.Ovf.I4.Un 将位于计算堆栈顶部的无符号值转换为有符号 int32,并在溢出时引发 OverflowException。
Conv.Ovf.I8 将位于计算堆栈顶部的有符号值转换为有符号 int64,并在溢出时引发 OverflowException。
Conv.Ovf.I8.Un 将位于计算堆栈顶部的无符号值转换为有符号 int64,并在溢出时引发 OverflowException。
Conv.Ovf.U 将位于计算堆栈顶部的有符号值转换为 unsigned native int,并在溢出时引发 OverflowException。
Conv.Ovf.U.Un 将位于计算堆栈顶部的无符号值转换为 unsigned native int,并在溢出时引发 OverflowException。
Conv.Ovf.U1 将位于计算堆栈顶部的有符号值转换为 unsigned int8 并将其扩展为 int32,并在溢出时引发 OverflowException。
Conv.Ovf.U1.Un 将位于计算堆栈顶部的无符号值转换为 unsigned int8 并将其扩展为 int32,并在溢出时引发 OverflowException。
Conv.Ovf.U2 将位于计算堆栈顶部的有符号值转换为 unsigned int16 并将其扩展为 int32,并在溢出时引发 OverflowException。
Conv.Ovf.U2.Un 将位于计算堆栈顶部的无符号值转换为 unsigned int16 并将其扩展为 int32,并在溢出时引发 OverflowException。
Conv.Ovf.U4 将位于计算堆栈顶部的有符号值转换为 unsigned int32,并在溢出时引发 OverflowException。
Conv.Ovf.U4.Un 将位于计算堆栈顶部的无符号值转换为 unsigned int32,并在溢出时引发 OverflowException。
Conv.Ovf.U8 将位于计算堆栈顶部的有符号值转换为 unsigned int64,并在溢出时引发 OverflowException。
Conv.Ovf.U8.Un 将位于计算堆栈顶部的无符号值转换为 unsigned int64,并在溢出时引发 OverflowException。
Conv.R.Un 将位于计算堆栈顶部的无符号整数值转换为 float32。
Conv.R4 将位于计算堆栈顶部的值转换为 float32。
Conv.R8 将位于计算堆栈顶部的值转换为 float64。
Conv.U 将位于计算堆栈顶部的值转换为 unsigned native int,然后将其扩展为 native int。
Conv.U1 将位于计算堆栈顶部的值转换为 unsigned int8,然后将其扩展为 int32。
Conv.U2 将位于计算堆栈顶部的值转换为 unsigned int16,然后将其扩展为 int32。
Conv.U4 将位于计算堆栈顶部的值转换为 unsigned int32,然后将其扩展为 int32。
Conv.U8 将位于计算堆栈顶部的值转换为 unsigned int64,然后将其扩展为 int64。
Cpblk 将指定数目的字节从源地址复制到目标地址。
Cpobj 将位于对象(&、* 或 native int 类型)地址的值类型复制到目标对象(&、* 或 native int 类型)的地址。
Div 将两个值相除并将结果作为浮点(F 类型)或商(int32 类型)推送到计算堆栈上。
Div.Un 两个无符号整数值相除并将结果 ( int32 ) 推送到计算堆栈上。
Dup 复制计算堆栈上当前最顶端的值,然后将副本推送到计算堆栈上。
Endfilter 将控制从异常的 filter 子句转移回公共语言结构 (CLI) 异常处理程序。
Endfinally 将控制从异常块的 fault 或 finally 子句转移回公共语言结构 (CLI) 异常处理程序。
Initblk 将位于特定地址的内存的指定块初始化为给定大小和初始值。
Initobj 将位于指定地址的值类型的每个字段初始化为空引用或适当的基元类型的 0。
Isinst 测试对象引用(O 类型)是否为特定类的实例。
Jmp 退出当前方法并跳至指定方法。
Ldarg 将参数(由指定索引值引用)加载到堆栈上。
Ldarg.0 将索引为 0 的参数加载到计算堆栈上。
Ldarg.1 将索引为 1 的参数加载到计算堆栈上。
Ldarg.2 将索引为 2 的参数加载到计算堆栈上。
Ldarg.3 将索引为 3 的参数加载到计算堆栈上。
Ldarg.S 将参数(由指定的短格式索引引用)加载到计算堆栈上。
Ldarga 将参数地址加载到计算堆栈上。
Ldarga.S 以短格式将参数地址加载到计算堆栈上。
Ldc.I4 将所提供的 int32 类型的值作为 int32 推送到计算堆栈上。
Ldc.I4.0 将整数值 0 作为 int32 推送到计算堆栈上。
Ldc.I4.1 将整数值 1 作为 int32 推送到计算堆栈上。
Ldc.I4.2 将整数值 2 作为 int32 推送到计算堆栈上。
Ldc.I4.3 将整数值 3 作为 int32 推送到计算堆栈上。
Ldc.I4.4 将整数值 4 作为 int32 推送到计算堆栈上。
Ldc.I4.5 将整数值 5 作为 int32 推送到计算堆栈上。
Ldc.I4.6 将整数值 6 作为 int32 推送到计算堆栈上。
Ldc.I4.7 将整数值 7 作为 int32 推送到计算堆栈上。
Ldc.I4.8 将整数值 8 作为 int32 推送到计算堆栈上。
Ldc.I4.M1 将整数值 -1 作为 int32 推送到计算堆栈上。
Ldc.I4.S 将提供的 int8 值作为 int32 推送到计算堆栈上(短格式)。
Ldc.I8 将所提供的 int64 类型的值作为 int64 推送到计算堆栈上。
Ldc.R4 将所提供的 float32 类型的值作为 F (float) 类型推送到计算堆栈上。
Ldc.R8 将所提供的 float64 类型的值作为 F (float) 类型推送到计算堆栈上。
Ldelem 按照指令中指定的类型,将指定数组索引中的元素加载到计算堆栈的顶部。
Ldelem.I 将位于指定数组索引处的 native int 类型的元素作为 native int 加载到计算堆栈的顶部。
Ldelem.I1 将位于指定数组索引处的 int8 类型的元素作为 int32 加载到计算堆栈的顶部。
Ldelem.I2 将位于指定数组索引处的 int16 类型的元素作为 int32 加载到计算堆栈的顶部。
Ldelem.I4 将位于指定数组索引处的 int32 类型的元素作为 int32 加载到计算堆栈的顶部。
Ldelem.I8 将位于指定数组索引处的 int64 类型的元素作为 int64 加载到计算堆栈的顶部。
Ldelem.R4 将位于指定数组索引处的 float32 类型的元素作为 F 类型(浮点型)加载到计算堆栈的顶部。
Ldelem.R8 将位于指定数组索引处的 float64 类型的元素作为 F 类型(浮点型)加载到计算堆栈的顶部。
Ldelem.Ref 将位于指定数组索引处的包含对象引用的元素作为 O 类型(对象引用)加载到计算堆栈的顶部。
Ldelem.U1 将位于指定数组索引处的 unsigned int8 类型的元素作为 int32 加载到计算堆栈的顶部。
Ldelem.U2 将位于指定数组索引处的 unsigned int16 类型的元素作为 int32 加载到计算堆栈的顶部。
Ldelem.U4 将位于指定数组索引处的 unsigned int32 类型的元素作为 int32 加载到计算堆栈的顶部。
Ldelema 将位于指定数组索引的数组元素的地址作为 & 类型(托管指针)加载到计算堆栈的顶部。
Ldfld 查找对象中其引用当前位于计算堆栈的字段的值。
Ldflda 查找对象中其引用当前位于计算堆栈的字段的地址。
Ldftn 将指向实现特定方法的本机代码的非托管指针(native int 类型)推送到计算堆栈上。
Ldind.I 将 native int 类型的值作为 native int 间接加载到计算堆栈上。
Ldind.I1 将 int8 类型的值作为 int32 间接加载到计算堆栈上。
Ldind.I2 将 int16 类型的值作为 int32 间接加载到计算堆栈上。
Ldind.I4 将 int32 类型的值作为 int32 间接加载到计算堆栈上。
Ldind.I8 将 int64 类型的值作为 int64 间接加载到计算堆栈上。
Ldind.R4 将 float32 类型的值作为 F (float) 类型间接加载到计算堆栈上。
Ldind.R8 将 float64 类型的值作为 F (float) 类型间接加载到计算堆栈上。
Ldind.Ref 将对象引用作为 O(对象引用)类型间接加载到计算堆栈上。
Ldind.U1 将 unsigned int8 类型的值作为 int32 间接加载到计算堆栈上。
Ldind.U2 将 unsigned int16 类型的值作为 int32 间接加载到计算堆栈上。
Ldind.U4 将 unsigned int32 类型的值作为 int32 间接加载到计算堆栈上。
Ldlen 将从零开始的、一维数组的元素的数目推送到计算堆栈上。
Ldloc 将指定索引处的局部变量加载到计算堆栈上。
Ldloc.0 将索引 0 处的局部变量加载到计算堆栈上。
Ldloc.1 将索引 1 处的局部变量加载到计算堆栈上。
Ldloc.2 将索引 2 处的局部变量加载到计算堆栈上。
Ldloc.3 将索引 3 处的局部变量加载到计算堆栈上。
Ldloc.S 将特定索引处的局部变量加载到计算堆栈上(短格式)。
Ldloca 将位于特定索引处的局部变量的地址加载到计算堆栈上。
Ldloca.S 将位于特定索引处的局部变量的地址加载到计算堆栈上(短格式)。
Ldnull 将空引用(O 类型)推送到计算堆栈上。
Ldobj 将地址指向的值类型对象复制到计算堆栈的顶部。
Ldsfld 将静态字段的值推送到计算堆栈上。
Ldsflda 将静态字段的地址推送到计算堆栈上。
Ldstr 推送对元数据中存储的字符串的新对象引用。
Ldtoken 将元数据标记转换为其运行时表示形式,并将其推送到计算堆栈上。
Ldvirtftn 将指向实现与指定对象关联的特定虚方法的本机代码的非托管指针(native int 类型)推送到计算堆栈上。
Leave 退出受保护的代码区域,无条件将控制转移到特定目标指令。
Leave.S 退出受保护的代码区域,无条件将控制转移到目标指令(缩写形式)。
Localloc 从本地动态内存池分配特定数目的字节并将第一个分配的字节的地址(瞬态指针,* 类型)推送到计算堆栈上。
Mkrefany 将对特定类型实例的类型化引用推送到计算堆栈上。
Mul 将两个值相乘并将结果推送到计算堆栈上。
Mul.Ovf 将两个整数值相乘,执行溢出检查,并将结果推送到计算堆栈上。
Mul.Ovf.Un 将两个无符号整数值相乘,执行溢出检查,并将结果推送到计算堆栈上。
Neg 对一个值执行求反并将结果推送到计算堆栈上。
Newarr 将对新的从零开始的一维数组(其元素属于特定类型)的对象引用推送到计算堆栈上。
Newobj 创建一个值类型的新对象或新实例,并将对象引用(O 类型)推送到计算堆栈上。
Nop 如果修补操作码,则填充空间。尽管可能消耗处理周期,但未执行任何有意义的操作。
Not 计算堆栈顶部整数值的按位求补并将结果作为相同的类型推送到计算堆栈上。
Or 计算位于堆栈顶部的两个整数值的按位求补并将结果推送到计算堆栈上。
Pop 移除当前位于计算堆栈顶部的值。
Prefix1 基础结构。此指令为保留指令。
Prefix2 基础结构。此指令为保留指令。
Prefix3 基础结构。此指令为保留指令。
Prefix4 基础结构。此指令为保留指令。
Prefix5 基础结构。此指令为保留指令。
Prefix6 基础结构。此指令为保留指令。
Prefix7 基础结构。此指令为保留指令。
Prefixref 基础结构。此指令为保留指令。
Readonly 指定后面的数组地址操作在运行时不执行类型检查,并且返回可变性受限的托管指针。
Refanytype 检索嵌入在类型化引用内的类型标记。
Refanyval 检索嵌入在类型化引用内的地址(& 类型)。
Rem 将两个值相除并将余数推送到计算堆栈上。
Rem.Un 将两个无符号值相除并将余数推送到计算堆栈上。
Ret 从当前方法返回,并将返回值(如果存在)从调用方的计算堆栈推送到被调用方的计算堆栈上。
Rethrow 再次引发当前异常。
Shl 将整数值左移(用零填充)指定的位数,并将结果推送到计算堆栈上。
Shr 将整数值右移(保留符号)指定的位数,并将结果推送到计算堆栈上。
Shr.Un 将无符号整数值右移(用零填充)指定的位数,并将结果推送到计算堆栈上。
Sizeof 将提供的值类型的大小(以字节为单位)推送到计算堆栈上。
Starg 将位于计算堆栈顶部的值存储到位于指定索引的参数槽中。
Starg.S 将位于计算堆栈顶部的值存储在参数槽中的指定索引处(短格式)。
Stelem 用计算堆栈中的值替换给定索引处的数组元素,其类型在指令中指定。
Stelem.I 用计算堆栈上的 native int 值替换给定索引处的数组元素。
Stelem.I1 用计算堆栈上的 int8 值替换给定索引处的数组元素。
Stelem.I2 用计算堆栈上的 int16 值替换给定索引处的数组元素。
Stelem.I4 用计算堆栈上的 int32 值替换给定索引处的数组元素。
Stelem.I8 用计算堆栈上的 int64 值替换给定索引处的数组元素。
Stelem.R4 用计算堆栈上的 float32 值替换给定索引处的数组元素。
Stelem.R8 用计算堆栈上的 float64 值替换给定索引处的数组元素。
Stelem.Ref 用计算堆栈上的对象 ref 值(O 类型)替换给定索引处的数组元素。
Stfld 用新值替换在对象引用或指针的字段中存储的值。
Stind.I 在所提供的地址存储 native int 类型的值。
Stind.I1 在所提供的地址存储 int8 类型的值。
Stind.I2 在所提供的地址存储 int16 类型的值。
Stind.I4 在所提供的地址存储 int32 类型的值。
Stind.I8 在所提供的地址存储 int64 类型的值。
Stind.R4 在所提供的地址存储 float32 类型的值。
Stind.R8 在所提供的地址存储 float64 类型的值。
Stind.Ref 存储所提供地址处的对象引用值。
Stloc 从计算堆栈的顶部弹出当前值并将其存储到指定索引处的局部变量列表中。
Stloc.0 从计算堆栈的顶部弹出当前值并将其存储到索引 0 处的局部变量列表中。
Stloc.1 从计算堆栈的顶部弹出当前值并将其存储到索引 1 处的局部变量列表中。
Stloc.2 从计算堆栈的顶部弹出当前值并将其存储到索引 2 处的局部变量列表中。
Stloc.3 从计算堆栈的顶部弹出当前值并将其存储到索引 3 处的局部变量列表中。
Stloc.S 从计算堆栈的顶部弹出当前值并将其存储在局部变量列表中的 index 处(短格式)。
Stobj 将指定类型的值从计算堆栈复制到所提供的内存地址中。
Stsfld 用来自计算堆栈的值替换静态字段的值。
Sub 从其他值中减去一个值并将结果推送到计算堆栈上。
Sub.Ovf 从另一值中减去一个整数值,执行溢出检查,并且将结果推送到计算堆栈上。
Sub.Ovf.Un 从另一值中减去一个无符号整数值,执行溢出检查,并且将结果推送到计算堆栈上。
Switch 实现跳转表。
Tailcall 执行后缀的方法调用指令,以便在执行实际调用指令前移除当前方法的堆栈帧。
Throw 引发当前位于计算堆栈上的异常对象。
Unaligned 指示当前位于计算堆栈上的地址可能没有与紧接的 ldind、stind、ldfld、stfld、ldobj、stobj、initblk 或 cpblk 指令的自然大小对齐。
Unbox 将值类型的已装箱的表示形式转换为其未装箱的形式。
Unbox.Any 将指令中指定类型的已装箱的表示形式转换成未装箱形式。
Volatile 指定当前位于计算堆栈顶部的地址可以是易失的,并且读取该位置的结果不能被缓存,或者对该地址的多个存储区不能被取消。
Xor 计算位于计算堆栈顶部的两个值的按位异或,并且将结果推送到计算堆栈上。


一个cron 表达式是由6或者7个以空格分隔的字段组成。字段能够包含任何允许值,伴随着特殊字符不同组合。这些字段如下所示:

字段名称 是否一定需要 允许的值 允许的特殊字符串
Seconds YES 0-59 , – * /
Minutes YES 0-59 , – * /
Hours YES 0-23 , – * /
Day of month YES 1-31 , – * ? / L W
Month YES 1-12 or JAN-DEC , – * /
Day of week YES 1-7 or SUN-SAT , – * ? / L #
Year NO empty, 1970-2099 , – * /

可见,一个简单的cron表达式可以如此* * * * ? *表达或者更复杂些,像这样

0/5 14,18,3-39,52 * ? JAN,MAR,SEPMON-FRI 2002-2010


  • * (“所有的值”) – 意味着所有值。
  • ? (“无特殊值”) – 当你需要指定两个字段中的一个时,这个字符很有用。比如,如果我想在一个月中的特别的一天(可以是第10号之类的)触发我的触发器,但是我并不关心它是哪个星期执行。这样的话,可以在月份字段上设置0,星期字段上设置问号(?)。
  • (“指定区间”)-  用来指定区间。比如,“10-12”在小时字段中,这意味着“10点、11点和12点”
  • (“定额外的值”)- 用来指定额外的值。比如,在星期字段中,“MON,WED,FRI”表示“星期一,星期三,星期五”。
  • / (“递增”)-用来指定递增量。比如,在秒字段上,“0/15”表示“0秒、15秒、30秒和45秒”。
  • L (“最后”) –在两个字段上,它们有不同的含义。比如,在天数字段上,“L”值意味着“一个月的最后一天”–平年中2月份的28号,一月的31号等。如果使用在星期 字段中,它简单地意味着“7”或者“星期六”。当使用“L”时,那是很重要的,不去指定列表或者区间,因为你将获得令人困惑的结果。
  • W (“星期”) – 使用来指定星期中最靠近给予的天。例如,我在天数字段上指定了“15W”,这就意味着“这个月离15号最近的某个星期”。如果15号是星期天,它将在周六 的14号触发。如果15好是一个星期二,那么他将在15号的星期二触发。在天字段上,“W”不会跳跃过月份来触发执行。在天数字段上,“L”和“W”能够 结合使用,表示的意思是:“月份的最后一个星期”
  • # (“天数”) –用来指定一个月的某一天。比如,在星期字段上,“6#3”表示“每月的第三个星期五”(6表示星期五,#3表示这个月的第三个)。如果像“#3”指定的话,它将不会触发。



0 0 12 * * ? Fire at 12pm (noon) every day
0 15 10 ? * * Fire at 10:15am every day
0 15 10 * * ? Fire at 10:15am every day
0 15 10 * * ? * Fire at 10:15am every day
0 15 10 * * ? 2005 Fire at 10:15am every day during the year 2005
0 * 14 * * ? Fire every minute starting at 2pm and ending at 2:59pm, every day
0 0/5 14 * * ? Fire every 5 minutes starting at 2pm and ending at 2:55pm, every day
0 0/5 14,18 * * ? Fire every 5 minutes starting at 2pm and ending at 2:55pm, AND fire every 5 minutes starting at 6pm and ending at 6:55pm, every day
0 0-5 14 * * ? Fire every minute starting at 2pm and ending at 2:05pm, every day
0 10,44 14 ? 3 WED Fire at 2:10pm and at 2:44pm every Wednesday in the month of March.
0 15 10 ? * MON-FRI Fire at 10:15am every Monday, Tuesday, Wednesday, Thursday and Friday
0 15 10 15 * ? Fire at 10:15am on the 15th day of every month
0 15 10 L * ? Fire at 10:15am on the last day of every month
0 15 10 ? * 6L Fire at 10:15am on the last Friday of every month
0 15 10 ? * 6L Fire at 10:15am on the last Friday of every month
0 15 10 ? * 6L 2002-2005 Fire at 10:15am on every last friday of every month during the years 2002, 2003, 2004 and 2005
0 15 10 ? * 6#3 Fire at 10:15am on the third Friday of every month
0 0 12 1/5 * ? Fire at 12pm (noon) every 5 days every month, starting on the first day of the month.
0 11 11 11 11 ? Fire every November 11th at 11:11am.


C# 中发送和接收MSMQ的一个示例

Ok, 直接上代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplication3
    public class Form1 : Form
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
            if (disposing && (components != null))

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
            this.button1 = new System.Windows.Forms.Button();
            this.button2 = new System.Windows.Forms.Button();
            this.label1 = new System.Windows.Forms.Label();
            this.button3 = new System.Windows.Forms.Button();
            this.label2 = new System.Windows.Forms.Label();
            // button1
            this.button1.Location = new System.Drawing.Point(198, 52);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(143, 23);
            this.button1.TabIndex = 0;
            this.button1.Text = "Send Message";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // button2
            this.button2.Location = new System.Drawing.Point(12, 52);
            this.button2.Name = "button2";
            this.button2.Size = new System.Drawing.Size(157, 23);
            this.button2.TabIndex = 1;
            this.button2.Text = "Received Message";
            this.button2.UseVisualStyleBackColor = true;
            this.button2.Click += new System.EventHandler(this.button2_Click);
            // label1
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(151, 94);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(31, 13);
            this.label1.TabIndex = 2;
            this.label1.Text = "none";
            // button3
            this.button3.Location = new System.Drawing.Point(366, 52);
            this.button3.Name = "button3";
            this.button3.Size = new System.Drawing.Size(157, 23);
            this.button3.TabIndex = 3;
            this.button3.Text = "Stop Receiveing";
            this.button3.UseVisualStyleBackColor = true;
            // label2
            this.label2.AutoSize = true;
            this.label2.Location = new System.Drawing.Point(12, 94);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(133, 13);
            this.label2.TabIndex = 4;
            this.label2.Text = "Received a new message:";
            // Form1
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(565, 146);
            this.Name = "Form1";
            this.Text = "Form1";



        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.Button button2;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Button button3;
        private System.Windows.Forms.Label label2;

        public Form1()

        private void button1_Click(object sender, EventArgs e)
            System.Messaging.MessageQueue mq;

            // 如果不存在google这个队列, 那么创建这个队列
            if (System.Messaging.MessageQueue.Exists(".\\google"))
                mq = new System.Messaging.MessageQueue(".\\google");
                mq = System.Messaging.MessageQueue.Create(".\\google");
            mq.Purge();// 将队列清空
            // 向队列中发送一条消息, 1个随机的数字, 并使用XML序列化进行传输
            mq.Send(new System.Messaging.Message(new Random().NextDouble(), new System.Messaging.XmlMessageFormatter()));


        private void Fn()
            var mq = new System.Messaging.MessageQueue(".\\google")
                Formatter = new System.Messaging.XmlMessageFormatter(new[] { typeof(double) })
            while (true)
                var message = mq.Receive();
                if (null != message)


        private delegate void Do(string str);

        private void Set(string str)
            label1.Text = str;

        // 默认情况下, Window form是不允许被非创建它的线程进行访问的, 所以要通过delegate进行安全调用
        // 可以通过设置 Control.CheckForIllegalCrossThreadCalls = false; 来取消跨线程调用的限制
        private void Invoke(string str)
            var foo = new Do(Set);
            // 可以看到: 是label1 这个控件去调用foo, 是种主动行为. 这样就不会引用跨线程调用异常
            label1.Invoke(foo, str);

        private void button2_Click(object sender, EventArgs e)
            // 创建一个新的线程去接收队列中的消息
            var ts = new ThreadStart(Fn);
            var th = new Thread(ts);
            MessageBox.Show(@"Start Receiving");


1. 首先得有队列, 在没有队列的情况下, new System.Messaging.MessageQueue(“.\\google”) 是会有异常的, 所以得先判断和创建队列通道:

            System.Messaging.MessageQueue mq;

            // 如果不存在google这个队列, 那么创建这个队列
            if (System.Messaging.MessageQueue.Exists(".\\google"))
                mq = new System.Messaging.MessageQueue(".\\google");
                mq = System.Messaging.MessageQueue.Create(".\\google");

2. window form 默认情况下不可被非创建它的线程进行访问. 这个问题, 简单点的方法是允许它被其它线程访问:

Control.CheckForIllegalCrossThreadCalls = false;

但是这样其实会有一个跨线程访问的风险存在, 是非安全的. 建议的方法是通过delegate的方法, 让控件自己去invoke需要调用的方法, 是一种由被动修改变为主动出击的方式.