Программы использующие инструкции верхнего уровня должны быть исполняемыми

When the output type is set to «Windows application» or «Console application», the service can’t be started and gives error «1053: The service did not respond to the start or control request in a timely fashion.». Digging behind that, I have found that it is due to Windows 10 (+ new Windows server versions) not allowing integration with the the UI (zero session) without the «Interactive Services Detection» service running — and that is not allowed in Windows 10.

Trying to solve that, using output type «Class library» instead, results in build error «CS8805: Program using top-level statements must be an executable.». I have read som articles saying that this can be due to a double semi-colon somewhere, but I haven’t found anything like that in the source files.

So — how do I create a Worker service which is functional on Windows 10 and new Windows servers?


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

  • ID: ae847f16-ff74-ab68-7163-f713b447430c
  • Version Independent ID: ff379d26-0d85-6d74-67da-4a3cd260b04d
  • Content: Worker Services in .NET
  • Content Source: docs/core/extensions/workers.md
  • Product: dotnet-fundamentals
  • GitHub Login: @IEvangelist
  • Microsoft Alias: dapine

C# logoИнструкции верхнего уровня (Top-Level Statement) позволяют отказаться от некоторых формальностей при написании приложений и сделать код проще. Возможно, это не очень будет заметно при написании сложных приложений, но может хорошо сэкономить время при проведении исследований, создании небольших утилит и прототипов.

Минимальная платформа с полной поддержкой: нет ограничений.

Так обычно выглядит точка входа в консольное приложение:

namespace Prototype
{
    public static class Program
    {
        public static int Main(string[] args)
        {
            bool success = DoSomeJob();
            return success ? 1 : -1;
        }
    }
}

В данном случае C# 9 позволяет отказаться от таких шаблонных деталей как namespace, class Program, метод Main(…) и сразу начать писать код точки входа.

var result = DoSomeJob();
return  result ? 1 : -1;

Такой код и называется инструкциями верхнего уровня.

Возможности

В инструкциях верхнего уровня можно:

  • обращаться к переменной string[] args, представляющей собой массив аргументов переданных через командную строку.
  • возвращать целочисленное (int) значение.
  • вызывать асинхронные методы.
  • объявлять локальные методы.
  • объявлять свои пространства имен и классы, но только после кода инструкций верхнего уровня.

При сборке проекта компилятор в глобальном пространстве имен (global namespace) автоматически создаст класс Program c одним из четырех вариантов метода Main(…), в зависимости от написанного кода:

  • void Main(string[] args)
  • int Main(string[] args)
  • Task Main(string[] args)
  • Task<int> Main(string[] args)

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

Рассмотрим описанные возможности на следующем примере:

using System;
using System.Threading.Tasks;
using DemoApp.Reader;

var reader = new FileReader();
string content = await reader.Read(GetFileName());
Console.WriteLine(content);

return content.Length;

string GetFileName() => args[0];

namespace DemoApp.Reader
{
    public class FileReader
    {
        public async Task Read(string fileName)
            => await System.IO.File.ReadAllTextAsync(fileName);
    }
}

Результат декомпиляции собранного проекта будет выглядеть следующим образом:

using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using DemoApp.Reader;

[CompilerGenerated]
internal class Program
{
    private static async Task<int> <Main>$(string[] args)
    {
        FileReader reader = new FileReader();
        string content = await reader.Read(GetFileName());
        Console.WriteLine(content);

        return content.Length;

        string GetFileName()
        {
            return args[0];
        }
    }
}

Отдельно, в пространстве имен DemoApp.Reader, можно найти класс FileReader. Его код, по сути, ничем не отличается от его объявления выше.

Ограничения

  • В проекте может быть только один файл с инструкциями верхнего уровня.
  • В проекте может быть только или объялена классическая точка входа (метод Main(…)) или указаны инструкции верхнего уровня.

Особенности

Несмотря на то, что класс Program автоматически генерируемый, на этапе разработки он доступен в приложении как и любой другой класс.

Кроме того, его можно расширить, добавив свои методы. Для этого необходимо самостоятельно объявить internal partial class Program. Соответственно, добавленные статические методы, будут также доступны в коде инструкций верхнего уровня.

Перепишем пример выше, заменив локальный метод GetFileName() на публичный статический:

using System;
using System.Threading.Tasks;
using DemoApp.Reader;

var reader = new FileReader();
string content = await reader.Read(GetFileName(args));
Console.WriteLine(content);

return content.Length;

internal partial class Program
{
    public static string GetFileName(string[] args)
        => args[0];
}

namespace DemoApp.Reader
{
    public class FileReader
    {
        public async Task Read(string fileName)
            => await System.IO.File.ReadAllTextAsync(fileName);
    }
}

Результат декомпиляции будет следующий:

// Program
using System;
using System.Threading.Tasks;
using DemoApp.Reader;

internal class Program
{
    private static async Task<int> <Main>$(string[] args)
    {
        FileReader reader = new FileReader();
        string content = await reader.Read(GetFileName(args));
        Console.WriteLine(content);

        return content.Length;
    }

    public static string GetFileName(string[] args)
    {
        return args[0];
    }
}

Служба может быть консольной или GUI .exe, это не имеет значения. Однако он ДОЛЖЕН вызывать сервисный API при запуске:

Когда диспетчер управления службами запускает процесс службы, он ожидает, пока процесс вызовет
StartServiceCtrlDispatcherфункция. Основной поток сервисного процесса должен сделать этот вызов как можно скорее после его запуска (в течение 30 секунд).

Подробнее о сервисах и смотрите примеры на MSDN…

Интерактивные службы устарели, начиная с Vista, но некоторая совместимость все еще сохранялась некоторое время. Это время закончилось, вам просто нужно написать сервис и вспомогательное приложение, которое запускается в сеансе пользователя.

Итак, я не долго программировал, поэтому у меня нет такого опыта, недавно я столкнулся с проблемой на replit.com, когда консоль распечатывала:

error CS8803: Top-level statements must precede namespace and type declarations.
using System;

Может кто подскажет проблему? Вот мой код, кому интересно:

int English;
int Science;
int AverageCalc;

AverageCalc = Convert.ToInt32(Console.ReadLine());

class Program {
  public static void Main (string[] args) {
    Console.WriteLine("Write your math grades");

      Math = Convert.ToInt32(Console.ReadLine());

      Console.WriteLine("Write your english grades");

      English = Convert.ToInt32(Console.ReadLine());

      Console.WriteLine("Write your science grades");

      Science = Convert.ToInt32(Console.ReadLine());

      AverageCalc = (Math+English+Science/3);
  }
}

if (AverageCalc > 80)
{
  Console.WriteLine("You passed with A mark!");
}
else if (AverageCalc < 80)
{
  Console.WriteLine("You passed with B mark!");
}
else if (AverageCalc < 65)
{
  Console.WriteLine("You passed with C mark!");
}
else if (AverageCalc < 60)
{
  Console.WriteLine("You passed with D mark!");
}
else if (AverageCalc < 55)
{
  Console.WriteLine("You got lower than D mark, try better next time.");
}

3 ответа

Как упоминал @Caius в своем ответе, вы исправляете оператор верхнего уровня и классический способ как в своем коде.

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

class Program
{
    public static void Main (string[] args) 
    {

И закрытие } класса Program и метода Main.

Пример взят из документации .

Ниже коды одинаковые.

using System;

namespace Application
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

TLS

Console.WriteLine("Hello, World!");


3

vivek nuna
17 Окт 2021 в 14:56

Вы смешиваете операторы верхнего уровня и не-TLS. По сути, TLS позволяет вам избавиться от всех namespace/class/static main и просто написать программу на C#, как если бы она была содержимым метода Main: docs.microsoft.com/en-us/dotnet/csharp/whats-new/ учебники/

По сути, это означает, что структура программы немного шаткая, потому что вы начинаете с некоторого TLS, затем переходите к более обычному стилю, а затем снова к TLS; отказ от структуры TLS (я бы посоветовал вам просто привыкнуть к пространству имен/классу/основному пуху; он все еще широко используется, и в некоторой степени это разумное введение в фигурные скобки и область видимости). Это выглядит как:

namespace X{
    class Program {
        public static void Main (string[] args) {
    
            int English;
            int Science;
            int AverageCalc;
        
            Console.WriteLine("Write your math grades");
        
            Math = Convert.ToInt32(Console.ReadLine());
        
            Console.WriteLine("Write your english grades");
        
            English = Convert.ToInt32(Console.ReadLine());
        
            Console.WriteLine("Write your science grades");
        
            Science = Convert.ToInt32(Console.ReadLine());
        
            AverageCalc = (Math+English+Science/3);
    
    
            if (AverageCalc > 80)
            {
              Console.WriteLine("You passed with A mark!");
            }
            else if (AverageCalc < 80)
            {
              Console.WriteLine("You passed with B mark!");
            }
            else if (AverageCalc < 65)
            {
              Console.WriteLine("You passed with C mark!");
            }
            else if (AverageCalc < 60)
            {
              Console.WriteLine("You passed with D mark!");
            }
            else if (AverageCalc < 55)
            {
              Console.WriteLine("You got lower than D mark, try better next time.");
            }

            Console.WriteLine("Press ENTER to exit (hah)");
            Console.ReadLine();
        }
    }
}

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

Вы забыли объявить переменную для math — я оставлю это вам в качестве упражнения, чтобы разобраться. Кроме того, когда переменные объявляются внутри метода (Main — это метод), вы должны именовать их, используя camelCase, а не PascalCase. Сейчас это может показаться неважным, но это условность, и следование ей поможет позже, когда код станет более сложным. PascalCase обычно используется для общедоступных методов, свойств, классов и имён, а camel — для частных или локальных.

Короче говоря, ваши переменные должны называться english, science и averageCalc.


3

Bellrampion
18 Авг 2022 в 00:39

C# требует, чтобы весь код находился внутри метода, а все методы — внутри класса.

Код

int English;
int Science;
int AverageCalc;

AverageCalc = Convert.ToInt32(Console.ReadLine());

Просто есть и компилятор не знает что с ним делать. Компилятор ожидает метод Main(), который вызывается первым, и оттуда должен вызываться весь остальной код.

Помните, что C# — это не язык сценариев, в котором операторы вычисляются один за другим сверху вниз.


0

John Alexiou
18 Авг 2022 в 00:47

Последнее обновление: 10.11.2021

Точкой входа в программу на языке C# является метод Main. Именно с этого метода начинается выполнение программы на C#. И программа на C# должна обязательно иметь метод Main. Однако может возникнуть вопрос, какой еще метод Main, если,
например, Visual Studio 2022 по умолчанию создает проект консольного приложения со следующим кодом:

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

И эта программа никаких методов Main не содержит, но при этом нормально выполняется и выводит на консоль строку «Hello, World!», как и запланировано. Это так называемая программа
верхнего уровня (top-level program). А вызов Console.WriteLine("Hello, World!") представляет инструкцию вехнего уровня (top-level statement)

Однако в реальности этот код неявно помещается компилятором в метод Main, который, в свою очередь, помещается в класс Program.
В действительности название класса может быть любым (как правило, это класс Program, собственно поэтому генерируемый по умолчанию файл кода
называется Program.cs). Но метод Main является обязательной частью консольного приложения. Поэтому выше представленный код фактически
эквивалентен следующей программе:

class Program
{
	static void Main(string[] args)
	{
		// See https://aka.ms/new-console-template for more information
		Console.WriteLine("Hello, World!");
	}
}

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

Возвращаемым типом метода Main обязательно является тип void. Кроме того, в качестве параметра он принимает массив строк — string[] args — в реальной программе это те параметры,
через которые при запуске программы из консоли мы можем передать ей некоторые значения. Внутри метода располагаются действия, которые выполняет программа.

До Visual Studio 2022 все предыдущие студии создавали по умолчанию примерно такой код. Но начиная с Visual Studio 2022
нам необязательно вручную определять класс Program и в нем метод Main — компилятор генерирует их самостоятельно.

Если мы определяем какие-то переменные, константы, методы и обращаемся к ним, они помещаются в метод Main. Например, следующая программа верхнего уровня

string hello = "Hello METANIT.COM";

Print(hello);

void Print(string message)
{
	Console.WriteLine(message);
}

будет аналогична следующей программе:

class Program
{
	static void Main(string[] args)
	{
		string hello = "Hello METANIT.COM";

		Print(hello);

		void Print(string message)
        {
			Console.WriteLine(message);
        }
	}
}

Если определяются новые типы, например, классы, то они помещаются вне класса Program. Например, код:

Person tom = new();
tom.SayHello();

class Person
{ 
    public void SayHello() =>Console.WriteLine("Hello");
}

будет аналогичен следующему

class Program
{
	static void Main(string[] args)
	{
		Person tom = new();
		tom.SayHello();
	}
}
class Person
{
	public void SayHello() => Console.WriteLine("Hello");
}

Однако надо учитывать, что опредления типов (в частности классов) должны идти в конце файла после инструкций верхнего уровня. То есть:

// инструкции верхнего уровня (top-level statements)
Person tom = new();
tom.SayHello();

// определение класса идет после инструкций верхнего уровня
class Person
{
    public void SayHello() => Console.WriteLine("Hello");
}

Таким образом, мы можем продолжать писать программы верхнего уровня без явного
определения метода Main. Либо мы можем явным образом определить метод Main и класс Program:

top-level programs in C# и Visual Studio 2022

И этот код будет выполняться аналогичным образом, как если бы мы не использовали класс Program и метод Main.

Понравилась статья? Поделить с друзьями:
  • Что делать после регистрации ип пошаговая инструкция 2020
  • Как сделать сводную диаграмму в excel пошаговая инструкция
  • Opti men от optimum nutrition инструкция по применению
  • I руководство по автокаду 2007
  • Лекарство кетостерил инструкция по применению цена