Постановка задачи

Всем привет, недавно столкнулся с  задачей проверки орфографии и исправления опечаток в 1С. Посмотрев варианты решений (MS Word, Yandex, т.д.), понял, что они мне не подходят. Решил копнуть глубже. Лично мне понравилось решение на базе спелчекера, встроенного в Windows. Поскольку в 1С нет возможности напрямую обратиться к этому функционалу ОС, я реализовал его в виде DLL на языке C# и сделал COM-обертку.  COM-объект подключил в 1С.

В итоге получилась вот такая простая форма, на которой при нажатии кнопки “Проверить опечатки” текст в Строке ввода анализируется и исправляется.

Исходный код COM объекта, обработки 1С и подробные пояснения приведены ниже.

Скачать исходники можно тут:

https://github.com/amizerov/SpellChecker

ПроверкаОрфографии.epf

Бинарики COM объекта тут:

https://mizer.dev/Files/SpellChecker.rar

Введение

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

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

В данной статье мы рассмотрим, как использовать интерфейсы COM (Component Object Model) Windows для интеграции встроенного чекера орфографии в свои проекты на языке C#. 

Покажу как опубликовать функционал поиска ошибок в виде своего COM компонента, и как его использовать в коде обработки 1С. Часто 1С-ники пытаются решить эту задачу с помощью SpellChecker  MS Word, но извините, это намного медленнее работает, чем тот способ, который описан ниже. 

Проект SpellChecker на C# в Visual Studio 2022

Для создания COM компоненты на C# используем шаблон проекта – Библиотека классов (Микрософт), я назвал проект SpellChecker.  Включим в него класс SpellCheckerBase, в котором просто объявим все интерфейсы Spell Checking – га из официальной документации Microsoft https://learn.microsoft.com/en-us/windows/win32/intl/spell-checker-interfaces

SpellCheckerBase.cs
using System.Runtime.InteropServices;

namespace SpellChecker;

public class SpellCheckerBase
{
    protected enum CORRECTIVE_ACTION
    {
        CORRECTIVE_ACTION_NONE,
        CORRECTIVE_ACTION_GET_SUGGESTIONS,
        CORRECTIVE_ACTION_REPLACE,
        CORRECTIVE_ACTION_DELETE,
    }

    [Guid("B7C82D61-FBE8-4B47-9B27-6C0D2E0DE0A3")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [ComImport]
    protected interface ISpellingError
    {
        uint StartIndex { get; }
        uint Length { get; }
        CORRECTIVE_ACTION CorrectiveAction { get; }
        string Replacement { [return: MarshalAs(UnmanagedType.LPWStr)] get; }
    }

    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("803E3BD4-2828-4410-8290-418D1D73C762")]
    [ComImport]
    protected interface IEnumSpellingError
    {
        [return: MarshalAs(UnmanagedType.Interface)]
        ISpellingError Next();
    }

    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("00000101-0000-0000-C000-000000000046")]
    [ComImport]
    protected interface IEnumString
    {
        void Next([In] uint celt, [MarshalAs(UnmanagedType.LPWStr)] out string rgelt, out uint pceltFetched);
        void Skip([In] uint celt);
        void Reset();
        void Clone([MarshalAs(UnmanagedType.Interface)] out IEnumString ppenum);
    }

    [Guid("432E5F85-35CF-4606-A801-6F70277E1D7A")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [ComImport]
    protected interface IOptionDescription
    {
        string Id { [return: MarshalAs(UnmanagedType.LPWStr)] get; }
        string Heading { [return: MarshalAs(UnmanagedType.LPWStr)] get; }
        string Description { [return: MarshalAs(UnmanagedType.LPWStr)] get; }
        IEnumString Labels { [return: MarshalAs(UnmanagedType.Interface)] get; }
    }

    [Guid("0B83A5B0-792F-4EAB-9799-ACF52C5ED08A")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [ComImport]
    protected interface ISpellCheckerChangedEventHandler
    {
        void Invoke([MarshalAs(UnmanagedType.Interface), In] ISpellChecker sender);
    }

    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("B6FD0B71-E2BC-4653-8D05-F197E412770B")]
    [ComImport]
    protected interface ISpellChecker
    {
        string languageTag { [return: MarshalAs(UnmanagedType.LPWStr)] get; }
        [return: MarshalAs(UnmanagedType.Interface)]
        IEnumSpellingError Check([MarshalAs(UnmanagedType.LPWStr), In] string text);
        [return: MarshalAs(UnmanagedType.Interface)]
        IEnumString Suggest([MarshalAs(UnmanagedType.LPWStr), In] string word);
        void Add([MarshalAs(UnmanagedType.LPWStr), In] string word);
        void Ignore([MarshalAs(UnmanagedType.LPWStr), In] string word);
        void AutoCorrect([MarshalAs(UnmanagedType.LPWStr), In] string from, [MarshalAs(UnmanagedType.LPWStr), In] string to);
        byte GetOptionValue([MarshalAs(UnmanagedType.LPWStr), In] string optionId);
        IEnumString OptionIds { [return: MarshalAs(UnmanagedType.Interface)] get; }
        string Id { [return: MarshalAs(UnmanagedType.LPWStr)] get; }
        string LocalizedName { [return: MarshalAs(UnmanagedType.LPWStr)] get; }
        uint add_SpellCheckerChanged([MarshalAs(UnmanagedType.Interface), In] ISpellCheckerChangedEventHandler handler);
        void remove_SpellCheckerChanged([In] uint eventCookie);
        [return: MarshalAs(UnmanagedType.Interface)]
        IOptionDescription GetOptionDescription([MarshalAs(UnmanagedType.LPWStr), In] string optionId);
        [return: MarshalAs(UnmanagedType.Interface)]
        IEnumSpellingError ComprehensiveCheck([MarshalAs(UnmanagedType.LPWStr), In] string text);
    }

    [Guid("8E018A9D-2415-4677-BF08-794EA61F94BB")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [ComImport]
    protected interface ISpellCheckerFactory
    {
        IEnumString SupportedLanguages { [return: MarshalAs(UnmanagedType.Interface)] get; }
        int IsSupported([MarshalAs(UnmanagedType.LPWStr), In] string languageTag);
        [return: MarshalAs(UnmanagedType.Interface)]
        ISpellChecker CreateSpellChecker([MarshalAs(UnmanagedType.LPWStr), In] string languageTag);
    }

    [Guid("7AB36653-1796-484B-BDFA-E74F1DB7C1DC")]
    [ComImport]
    protected class SpellCheckerFactoryClass
    {
    }
}

Добавим в проект класс SpellCheckerAPI, который унаследуем от SpellCheckerBase и реализуем в нем наш единственный, основной статический метод для проверки орфографии SpellCheck с удобным для нас результатом в виде списка объектов типа SpellCheckResult. Для краткости записи объявлен рекордом.

public record SpellCheckResult(string Word, string Action, string Replacement, List<string> Suggestions);

Вот такой список вернем List<SpellCheckResult>, это список сразу для всех слов с ошибками из анализируемого текста.

Word здесь это само слово с ошибкой, найденное в тексте, Action – одно из предлагаемых действий (заменить, удалить или игнорить), Replacement – основное слово для замены ошибочного и Suggestions – список всех вариков замены.

SpellCheckerAPI.cs
using System.Runtime.InteropServices;

namespace SpellChecker;

public record SpellCheckResult(string Word, string Action, string Replacement, List<string> Suggestions);

public class SpellCheckerAPI : SpellCheckerBase
{
    public static List<SpellCheckResult> SpellCheck(string s)
    {
        SpellCheckerFactoryClass? factory = null;
        ISpellCheckerFactory? ifactory = null;
        ISpellChecker? checker = null;
        ISpellingError? error = null;
        IEnumSpellingError? errors = null;
        IEnumString? suggestions = null;

        List<SpellCheckResult> spellCheckResults = new();

        try
        {
            factory = new SpellCheckerFactoryClass();
            ifactory = (ISpellCheckerFactory)factory;

            //проверим поддержку русского языка
            int res = ifactory.IsSupported("ru-RU");
            if (res == 0) { throw new Exception("Fatal error: russian language not supported!"); }

            checker = ifactory.CreateSpellChecker("ru-RU");

            errors = checker.Check(s);
            while (true)
            {
                //получаем ошибку
                if (error != null) { Marshal.ReleaseComObject(error); error = null; }
                error = errors.Next();
                if (error == null) break;

                //получаем слово с ошибкой
                string word = s.Substring((int)error.StartIndex, (int)error.Length);
                string action = "";
                string replac = error.Replacement;
                List<string> sugges = new();

                //получаем рекомендуемое действие
                switch (error.CorrectiveAction)
                {
                    case CORRECTIVE_ACTION.CORRECTIVE_ACTION_DELETE:
                        action = "удалить";
                        break;

                    case CORRECTIVE_ACTION.CORRECTIVE_ACTION_REPLACE:
                        action = "заменить";
                        break;

                    case CORRECTIVE_ACTION.CORRECTIVE_ACTION_GET_SUGGESTIONS:
                        action = "заменить на одно из";

                        if (suggestions != null) { Marshal.ReleaseComObject(suggestions); suggestions = null; }

                        //получаем список слов, предложенных для замены
                        suggestions = checker.Suggest(word);

                        while (true)
                        {
                            string suggestion;
                            uint count = 0;
                            suggestions.Next(1, out suggestion, out count);
                            if (count == 1) sugges.Add(suggestion);
                            else break;
                        }
                        break;
                }

                if(replac == "") replac = sugges.Count > 0 ? sugges[0] : "";
                spellCheckResults.Add(new SpellCheckResult(word, action, replac, sugges));
            }
        }
        finally
        {
            if (suggestions != null) { Marshal.ReleaseComObject(suggestions); }
            if (factory != null) { Marshal.ReleaseComObject(factory); }
            if (ifactory != null) { Marshal.ReleaseComObject(ifactory); }
            if (checker != null) { Marshal.ReleaseComObject(checker); }
            if (error != null) { Marshal.ReleaseComObject(error); }
            if (errors != null) { Marshal.ReleaseComObject(errors); }
        }

        return spellCheckResults;
    }
}

Теперь, для всего этого надо сделать COM обертку, для этого, в том же пространстве имен проекта SpellChecker, создадим еще один класс ComService с атрибутом [ComVisible(true)] и назначим ему уникальный Guid.

Реализуем в нем публичный метод SpellCheck, который и будет доступен через COM. Пусть, для простаты, он возвращает JSON строку с результатами проверки, ошибочными словами и предложениями по замене ошибочных слов.

using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.RegularExpressions;

namespace SpellChecker;

[ComVisible(true)]
[Guid("fe103d6e-e71b-414c-80bf-982f18f23237")]
public class ComService
{
    public string SpellCheck(string text)
    {
        var spellCheckResults = SpellCheckerAPI.SpellCheck(text);
        string x = JsonSerializer.Serialize(spellCheckResults);
        string jsonString = Regex.Unescape(x);

        return jsonString;
    }
}

В файле описания проекта добавим строку <EnableComHosting>true</EnableComHosting>, которая скажет компилятору добавить COM обертку для нашей библиотеки.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <EnableComHosting>true</EnableComHosting>
  </PropertyGroup>

</Project>

После этого, при сборке проекта, компилятор сгенерит дополнительный файлик SpellChecker.comhost.dll.

В нем компилятор реализует метод DllRegisterServer, что позволит нам зарегистрировать наш компонет в реестре с помощью утилиты regsvr32.

Вот что должно получиться в папке на выходе при компиляции:

Получается вроде как SpellChecker.comhost.dll тут основной, но без SpellChecker.dll и SpellChecker.runtimeconfig.json, без этих 2-х файлов он не работает, это просто обертка, всю полезную работу все таки делает SpellChecker.dll

Регистрация COM объекта

Итак, регистрируем файлик SpellChecker.comhost.dll в реестре утилитой regsvr32, из командной строки, запущенной с правами администратора.

Надо просто запустить команду: regsvr32 SpellChecker.comhost.dll в той директории где лежит наша библиотека. Очень важно отметить, что в папке должны быть обязательно 3-и файла:

  1. SpellCheck.comhost.dll

  2. SpellCheck.dll

  3. SpellCheck.runtimeconfig.json

Иначе regsvr32 выдаст ошибку. Файлы SpellCheck.pdb и второй json не влияют, но можно тоже оставить.

Если все правильно сделали, то увидим окно с Успешным выполнением регистрации объекта, и в реестре появится запись для нашего нового ProgID со значением SpellChecker.ComService

Обработка 1С

Теперь в коде 1С мы можем использовать функционал, реализованный в нашей DLL, подключая COM объект обычным манером, как говорится, поздним связыванием:

srv = Новый COMОбъект(“SpellChecker.ComService”);

и далее, вызвав метод srv.SpellCheck, получим результат в виде JSON строки таким образом:

res = srv.SpellCheck(txt);

результат превратим в массив объектов:

obj = ПростоеЧтениеJSON(res);

Где obj это массив объектов, содержащих ошибочное слово obj[i].Word и списков вариантов для его замены в виде массива строк obj[i].Suggestions. Еще одно свойство obj[i].Replacement – это основной варик замены слова с ошибкой.

Ниже полный код обработки:

ПроверкаОрфографии.epf
&НаКлиенте
Перем ОшибкаОрфографии;


&НаКлиенте
Процедура ПроверитьОпечатки(Команда)
	ОшибкаОрфографии = Ложь;
	ПроверкаОрфографии();
КонецПроцедуры

&НаКлиенте
Процедура ПроверкаОрфографии()

	txt = СтрокаВвода;
	srv = Новый COMОбъект("SpellChecker.ComService");
	res = srv.SpellCheck(txt);

	Если СтрДлина(res) = 2 Тогда
		Если ОшибкаОрфографии Тогда
			Оповещение = Новый ОписаниеОповещения("ПродолжитьОбработкуТекста", ЭтотОбъект);
			ПоказатьЗначение(Оповещение,
				"Отлично!" + Символы.ПС + "  Все ошибки исправлены!");
			ОшибкаОрфографии = Ложь;
		Иначе
			ПродолжитьОбработкуТекста();
		КонецЕсли
	Иначе
		ОшибкаОрфографии = Истина;
		obj = ПростоеЧтениеJSON(res);
		wor = obj[0].Word;
		rep = obj[0].Replacement;
		Оповещение = Новый ОписаниеОповещения("ПослеЗакрытияВопроса", ЭтотОбъект, obj[0]);
		ПоказатьВопрос(Оповещение, "Заменить " + wor + " на " + rep + "?",
								РежимДиалогаВопрос.ДаНет,,, "Ошибка орфографии");
	КонецЕсли

КонецПроцедуры

&НаКлиенте
Процедура ПослеЗакрытияВопроса(Результат, Параметры) Экспорт

    Если Результат = КодВозвратаДиалога.Да Тогда
		txt = СтрокаВвода;
		wor = Параметры.Word;
		rep = Параметры.Replacement;

		СтрокаВвода = СтрЗаменить(txt, wor, rep);

		ПроверкаОрфографии();
	Иначе
		ПродолжитьОбработкуТекста();
    КонецЕсли;

КонецПроцедуры

Функция ПростоеЧтениеJSON(Данные)

	ЧтениеJSON = Новый ЧтениеJSON;
	ЧтениеJSON.УстановитьСтроку(Данные);
	Возврат ПрочитатьJSON(ЧтениеJSON);

КонецФункции

&НаКлиенте
Процедура ПродолжитьОбработкуТекста(Параметры = Неопределено) Экспорт
	// Что то делаем дальше
КонецПроцедуры

Для примера, формочку в обработке я слепил вот так, без изысков, вы уже можете доработать ее как вам будет удобно для вашей задачи

И теперь, если правильно дать имена всем элементам на форме, то при нажатии на кнопку “Проверить опечатки”, код обработки, приведенный выше, последовательно исправит все ошибочные слова в поле ввода.

Заключение

Я понимаю, что COM технология наверно устарела, все уже перешли на вэб сервисы, REST API, gRPC, Message Brokers, WebSockets и GraphQL, но я решил попробовать интегрировать C# в 1С напрямую, а поскольку вставить C# dll в 1С без COM не получилось, завернул ее в COM. И кстати, как я не пытался добавить в COM объект SpellChecker событие OnComplete, ничего не получилось. Если кому интересно, вот такой код

using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.RegularExpressions;

namespace SpellCheckerV2;


[ComVisible(true)]
[Guid("fe103d6e-e71b-414c-80bf-982f18f23235"),
    InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IComService
{
    string SpellCheck(string text);
}

[ComVisible(true)]
[Guid("fe103d6e-e71b-414c-80bf-982f18f23236"),
    InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ISpellCheckerEvents
{
    [DispId(1)]
    public void OnComplete(string msg);
}

[ComVisible(true)]
[Guid("fe103d6e-e71b-414c-80bf-982f18f23238")]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(ISpellCheckerEvents))]
public class ComService : IComService
{
    [ComVisible(true)]
    public Action<string>? OnComplete;

    public string SpellCheck(string text)
    {
        var spellCheckResults = SpellCheckerAPI.SpellCheck(text);
        string x = JsonSerializer.Serialize(spellCheckResults);
        string jsonString = Regex.Unescape(x);

        OnComplete?.Invoke("Ok");
        return jsonString;
    }
}

Этот код не работает, не публикует, как предполагается, событие OnComplete, 1С его не видит. И соответственно, если в 1С написать:

srv = Новый COMОбъект(“SpellChecker.ComService”);
ДобавитьОбработчик srv.OnComplete, Пикака;

в строке где ДобавитьОбработчик пытается найти событие srv.OnComplete, возникает ошибка.

Ну, как говорится, отрицательный результат тоже результат.

Пока друзья, надеюсь на жесткий обс@р.

Комментарии (28)


  1. DMGarikk
    15.07.2024 14:21

    А что делать тем у кого 1С на импортозамещенном линуксе работает?


    1. Omankit
      15.07.2024 14:21

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


      1. DMGarikk
        15.07.2024 14:21

        отлично, я понимаю что можно и вручную проверять с орфографическим словарем

        Я вообще клоню к тому что COM интерфейс в винде можно считать устаревшим, а в рамках существования Windows/Linux клиентов у 1С - особенно, его использование вообще должно быть только в самом крайнем случае

        собственно это посыл автору статьи


        1. Nicotino Автор
          15.07.2024 14:21

          да я не против, делайте вэб сервис, COM обертка в этом случае не главное, а вот проверять опечатки Вордом конечно круче


    1. Nicotino Автор
      15.07.2024 14:21

      да, не судьба однако


  1. Omankit
    15.07.2024 14:21

    А почему у вас dll не в макете зашита? Зачем эти пляски с регистрацией dll в ОС?


    1. starik-2005
      15.07.2024 14:21

      Патамушта она СОМ, а не НАТИВ, наверное, но это не точно.


      1. 2128507
        15.07.2024 14:21

        помоему, 1С умеет импортировать COM объекты прямо из указанной библиотеки, я тоже не понял зачем ему глобальная доступность


    1. Nicotino Автор
      15.07.2024 14:21

      а что значит в макете зашита? это куда?


      1. Omankit
        15.07.2024 14:21

        Ну берете dll сохраняете в макет. Вызываете из макета как com объект и используете его функции. В итоге все хранится в конфигурации в базе данных.




  1. artptr86
    15.07.2024 14:21
    +2

    Иронично, что в самой статье полно грамматических и пунктуационных ошибок


    1. Nicotino Автор
      15.07.2024 14:21

      Ой прастите, извените, вы новерно даже не смогли дочетать до конца от отвращения)


      1. 2128507
        15.07.2024 14:21
        +1

        дочетать мы смогли (гыыыыыы), и зометили, што у вас на последнем камшоте вместо зилёной галочки трёхугольный знак опасносте.


      1. DMGarikk
        15.07.2024 14:21

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


        1. Nicotino Автор
          15.07.2024 14:21

          Ну ладно, обс@р принят, согласен. Но Вы хоть пример с ошибкой приведите пожалуйста, я исправлю. Даже специально проверил, вроде ничего редактор красным не подчеркивает.


          1. artptr86
            15.07.2024 14:21
            +1

            Для начала вы уже сами исправили некоторые ошибки, например, в предложении «Решил копнуть глубже» (было «капнуть»). Собственно, эта ошибка в первом абзаце и бросилась в глаза, раскрывая иронию темы. Но вы сами уже исправили её, отчего замечание «я проверил, ошибок не нашёл» выглядит несколько неспортивно.

            Если так угодно, вот пунктуационные ошибки в первых же абзацах. Так и быть, к повсеместному употреблению слова «функционал» я уже не придирался :)

            Дальше тоже есть ошибки, в том числе, например, в выражении «это на много медленнее» слово «намного» пишется слитно. Также часто встречаются двойные пробелы, которые создают впечатление неряшливости.

            Кстати говоря, эти ошибки порождали слова, имеющие в словаре, поэтому простая проверка по словарю не могла их найти.

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


            1. Nicotino Автор
              15.07.2024 14:21

              хорошо, спасибо


  1. 2128507
    15.07.2024 14:21

    Этот код не работает, не публикует, как предполагается, событие OnComplete, 1С его не видит. И соответственно, если в 1С написать:

    srv = Новый COMОбъект(“SpellChecker.ComService”);
    ДобавитьОбработчик srv.OnComplete, Пикака;

    в строке где ДобавитьОбработчик пытается найти событие srv.OnComplete, возникает ошибка.

    Ну, как говорится, отрицательный результат тоже результат.

    Пока друзья, надеюсь на жесткий обс@р.

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


    1. Nicotino Автор
      15.07.2024 14:21

      Брателло), ты маленько про COM почитай сначала, а то обс@р не в тему получается)


    1. Nicotino Автор
      15.07.2024 14:21

      да, кстати, я не пользуюсь делегатами для объявления события в шарпе последних версий, делаю это одной строкой public event Action<string> OnComplete


      1. 2128507
        15.07.2024 14:21

        это объявление, а где реализация?


        1. Nicotino Автор
          15.07.2024 14:21

          извините реализация чего? события? событие реализуется на клиенте

          но если вы имеете в виду реализацию метода интерфейса, то в COM для событий применяется такая хитрость - в интерфейсе просто объявляем метод, а в классе тоже объявляем одноименное событие, ну и добавляем атрибут классу[ComSourceInterfaces(typeof(ISpellCheckerEvents))]

          как бы ссылку на интерфейс, не наследуя, а просто указывая на связь

          Но к сожалению, похоже сейчас в новом .Net 8 это уже не работает


          1. 2128507
            15.07.2024 14:21

            так, стоп, это уже я увлекся и чушь начал пороть, сорян) Странно, что не работает, кстати...

            ПС. посмотрел на микросовте вот тут https://learn.microsoft.com/en-us/dotnet/api/system.action-1?view=net-8.0 - они делают через делегаты, дотнет 8.


  1. Fangaro
    15.07.2024 14:21

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

    Если для наименований в карточках, то есть же какой то первоисточник. И там могут отсутствовать орфографические ошибки, но присутствовать смысловые. Если нужны примеры, то в автосервисах их - рядами и колоннами. Особенно теперь, когда идёт перевод с первоисточника на английский, а уже с английского на русский.

    Если же это про проверку написания команд - тогда прошу извинить. Это та сфера, в которой я ничего не умею.

    Кстати! Обратите внимание на текст самой статьи. Вам не зря критические отзывы прилетели. Капать и копать имеют разный смысл. Как и слова простота и простата.


    1. Nicotino Автор
      15.07.2024 14:21

      спасибо!)) сорри, да)) по делу, с капать я оплошал), исправлю сейчас.

      и, кстати да, этот текст не про ошибки в коде, это пожалуй не мой уровень


      1. viruseg
        15.07.2024 14:21

        В превью статьи ещё осталось "капнуть".