Как компиляция C # обходится без заголовочных файлов?

Это очень трудный вопрос. Чтобы быть абсолютно честным, я понятия не имел в контексте JavaScript , поэтому я спросил в esdiscuss и получил убедительный ответ от Domenic .

WeakSets полезны для аргументов security и validation . Если вы хотите изолировать фрагмент JavaScript. Они позволяют вам помечать объект, чтобы указать, что он принадлежит к определенному набору объектов.

Предположим, у меня есть класс ApiRequest:

class ApiRequest {
  constructor() {
    // bring object to a consistent state, use platform code you have no cirect access to
  }

  makeRequest() {
    // do work 
  }
}

Теперь я написание платформы JavaScript - моя платформа позволяет запускать JavaScript для совершения звонков - чтобы сделать эти вызовы вам нужно ApiRequest - я хочу, чтобы вы сделали ApiRequest с объектами, которые я вам даю, чтобы вы не могли обойти любые

Однако на данный момент ничто не мешает вам:

ApiRequest.prototype.makeRequest.call(null, args); // make request as function
Object.create(ApiRequest.prototype).makeRequest(); // no initialization
function Foo(){}; Foo.prototype = ApiRequest.prototype; new Foo().makeRequest(); // no super

И так далее, обратите внимание, что вы не можете сохранить обычный список или массив объектов ApiRequest, поскольку это предотвратит их сбор мусора. Помимо закрытия, все может быть достигнуто с помощью общедоступных методов, таких как Object.getOwnPropertyNames или Object.getOwnSymbols. Итак, вы подходите ко мне и делаете:

const requests = new WeakSet();
class ApiRequest {
  constructor() {
    requests.add(this);
  }

  makeRequest() {
    if(!request.has(this)) throw new Error("Invalid access");
    // do work
  }
}

Теперь, независимо от того, что я делаю, я должен содержать действительный объект ApiRequest для вызова метода makeRequest. Это невозможно без WeakMap / WeakSet.

Итак, короче говоря - WeakMaps полезны для написания платформ в JavaScirpt. Обычно этот вид проверки выполняется на стороне C ++, но добавление этих функций позволит перемещать и создавать вещи в JavaScript.

(Конечно, все, что WeakSet делает WeakMap, который отображает значения в true, но это верно для любой конструкции map / set)

(Как говорит Берги, никогда не бывает причин добавлять литерал объекта непосредственно к WeakMap или WeakSet )

29
задан Community 23 May 2017 в 10:29
поделиться

5 ответов

Я вижу, что есть несколько интерпретаций вопроса. Я ответил на интерпретацию внутри решения, но позвольте мне заполнить его всей известной мне информацией.

«Метаданные файла заголовка» присутствуют в скомпилированных сборках, поэтому любая сборка, на которую вы добавляете ссылку, позволит компилятору выполнить извлекать из них метаданные.

Что касается вещей, которые еще не скомпилированы, части текущего решения, оно будет выполнять двухпроходную компиляцию, первое чтение пространств имен, имен типов, имен членов, т.е. все, кроме кода. Затем, когда это подтвердится, он прочитает код и скомпилирует его.

Это позволяет компилятору знать, что существует, а что не существует (в его вселенной).

Чтобы увидеть двухпроходный компилятор в действии , проверьте следующий код с тремя проблемами, двумя проблемами, связанными с объявлением, и одна проблема кода:

using System;

namespace ConsoleApplication11
{
    class Program
    {
        public static Stringg ReturnsTheWrongType()
        {
            return null;
        }

        static void Main(string[] args)
        {
            CallSomeMethodThatDoesntExist();
        }

        public static Stringg AlsoReturnsTheWrongType()
        {
            return null;
        }
    }
}

Обратите внимание, что компилятор будет жаловаться только на два типа Stringg , которые он не может найти. Если вы их исправите, он будет жаловаться на имя метода, вызванное в методе Main, которое не может найти.

38
ответ дан 28 November 2019 в 00:36
поделиться

UPDATE: This question was the subject of my blog for February 4th 2010. Thanks for the great question!

Let me lay it out for you. In the most basic sense the compiler is a "two pass compiler" because the phases that the compiler goes through are:

1) Generation of metadata. 2) Generation of IL.

Metadata is all the "top level" stuff that describes the structure of the code. Namespaces, classes, structs, enums, interfaces, delegates, methods, type parameters, formal parameters, constructors, events, attributes, and so on. Basically, everything except method bodies.

IL is all the stuff that goes in a method body -- the actual imperative code, rather than metadata about how the code is structured.

The first phase is actually implemented via a great many passes over the sources. It's way more than two.

The first thing we do is take the text of the sources and break it up into a stream of tokens. That is, we do lexical analysis to determine that

class c : b { }

is class, identifier, colon, identifier, left curly, right curly.

We then do a "top level parse" where we verify that the token streams define a grammaticaly-correct C# program. However, we skip parsing method bodies. When we hit a method body, we just blaze through the tokens until we get to the matching close curly. We'll come back to it later; we only care about getting enough information to generate metadata at this point.

We then do a "declaration" pass where we make notes about the location of every namespace and type declaration in the program.

We then do a pass where we verify that all the types declared have no cycles in their base types. We need to do this first because in every subsequent pass we need to be able to walk up type hierarchies without having to deal with cycles.

We then do a pass where we verify that all generic parameter constraints on generic types are also acyclic.

We then do a pass where we check whether every member of every type -- methods of classes, fields of structs, enum values, and so on -- is consistent. No cycles in enums, every overriding method overrides something that is actually virtual, and so on. At this point we can compute the "vtable" layouts of all interfaces, classes with virtual methods, and so on.

We then do a pass where we work out the values of all "const" fields.

At this point we have enough information to emit almost all the metadata for this assembly. We still do not have information about the metadata for iterator/anonymous function closures or anonymous types; we do those late.

We can now start generating IL. For each method body (and properties, indexers, constructors, and so on), we rewind the lexer to the point where the method body began and parse the method body.

Once the method body is parsed, we do an initial "binding" pass, where we attempt to determine the types of every expression in every statement. We then do a whole pile of passes over each method body.

We first run a pass to transform loops into gotos and labels.

(The next few passes look for bad stuff.)

Then we run a pass to look for use of deprecated types, for warnings.

Then we run a pass that searches for uses of anonymous types that we haven't emitted metadata for yet, and emit those.

Then we run a pass that searches for bad uses of expression trees. For example, using a ++ operator in an expression tree.

Then we run a pass that looks for all local variables in the body that are defined, but not used, to report warnings.

Then we run a pass that looks for illegal patterns inside iterator blocks.

Then we run the reachability checker, to give warnings about unreachable code, and tell you when you've done something like forgotten the return at the end of a non-void method.

Then we run a pass that verifies that every goto targets a sensible label, and that every label is targetted by a reachable goto.

Then we run a pass that checks that all locals are definitely assigned before use, notes which local variables are closed-over outer variables of an anonymous function or iterator, and which anonymous functions are in reachable code. (This pass does too much. I have been meaning to refactor it for some time now.)

At this point we're done looking for bad stuff, but we still have way more passes to go before we sleep.

Next we run a pass that detects missing ref arguments to calls on COM objects and fixes them. (This is a new feature in C# 4.)

Then we run a pass that looks for stuff of the form "new MyDelegate(Foo)" and rewrites it into a call to CreateDelegate.

Then we run a pass that transforms expression trees into the sequence of factory method calls necessary to create the expression trees at runtime.

Then we run a pass that rewrites all nullable arithmetic into code that tests for HasValue, and so on.

Then we run a pass that finds all references of the form base.Blah() and rewrites them into code which does the non-virtual call to the base class method.

Then we run a pass which looks for object and collection initializers and turns them into the appropriate property sets, and so on.

Then we run a pass which looks for dynamic calls (in C# 4) and rewrites them into dynamic call sites that use the DLR.

Then we run a pass that looks for calls to removed methods. (That is, partial methods with no actual implementation, or conditional methods that don't have their conditional compilation symbol defined.) Those are turned into no-ops.

Then we look for unreachable code and remove it from the tree. No point in codegenning IL for it.

Then we run an optimization pass that rewrites trivial "is" and "as" operators.

Then we run an optimization pass that looks for switch(constant) and rewrites it as a branch directly to the correct case.

Then we run a pass which turns string concatenations into calls to the correct overload of String.Concat.

(Ah, memories. These last two passes were the first things I worked on when I joined the compiler team.)

Then we run a pass which rewrites uses of named and optional parameters into calls where the side effects all happen in the correct order.

Then we run a pass which optimizes arithmetic; for example, if we know that M() returns an int, and we have 1 * M(), then we just turn it into M().

Then we do generation of the code for anonymous types first used by this method.

Then we transform anonymous functions in this body into methods of closure classes.

Finally, we transform iterator blocks into switch-based state machines.

Then we emit the IL for the transformed tree that we've just computed.

Easy as pie!

93
ответ дан 28 November 2019 в 00:36
поделиться

Он использует метаданные из эталонных сборок. Он содержит объявление полного типа, то же самое, что и в файле заголовка.

Это двухпроходный компилятор выполняет кое-что еще: вы можете использовать тип в одном исходном файле, прежде чем он будет объявлен в другом исходном коде. файл.

5
ответ дан 28 November 2019 в 00:36
поделиться

Это двухпроходный компилятор. http://en.wikipedia.org/wiki/Multi-pass_compiler

1
ответ дан 28 November 2019 в 00:36
поделиться

Всю необходимую информацию можно получить из указанных сборок.

Итак, файлов заголовков нет, но компилятору нужен доступ к используемой DLL.

И да, это двухпроходный компилятор, но это не объясняет, как он получает информацию о типах библиотек.

1
ответ дан 28 November 2019 в 00:36
поделиться
Другие вопросы по тегам:

Похожие вопросы: