Nano Hash - криптовалюты, майнинг, программирование

Делегаты действий, дженерики, ковариантность и контравариантность

У меня есть два класса бизнес-контрактов:

public BusinessContract

public Person : BusinessContract

В другом классе у меня есть следующий код:

private Action<BusinessContract> _foo;

public void Foo<T>( Action<T> bar ) where T : BusinessContract
{
    _foo = bar;
}

Вышеприведенное даже не скомпилируется, что меня немного сбивает с толку. Я ограничиваю T как BusinessContract, так почему же компилятор не знает, что bar может быть присвоен _foo?

Пытаясь обойти это, мы попытались изменить его на следующее:

public void Foo<T>( Action<T> bar ) where T : BusinessContract
{
    _foo = (Action<BusinessContract>)bar;
}

Теперь компилятор доволен, поэтому я пишу следующий код в другом месте своего приложения:

Foo<Person>( p => p.Name = "Joe" );

И приложение взрывается InvalidCastException во время выполнения.

Я не понимаю. Разве я не могу преобразовать свой более конкретный тип в менее конкретный тип и назначить его?

ОБНОВИТЬ

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

private Action<BusinessContract> _foo;

public void Foo<T>( Action<T> bar ) where T : BusinessContract
{
    _foo = contract => bar( (T)contract );
}

Зачем мы это делаем? У нас есть поддельный DAL, который мы используем для модульного тестирования. С одним из методов нам нужно дать разработчику теста возможность указать, что должен делать метод, когда он вызывается во время теста (это метод обновления, который обновляет кэшированный объект из базы данных). Цель Foo — установить, что должно происходить при вызове обновления. IOW, в другом месте этого класса у нас есть следующее.

public void Refresh( BusinessContract contract )
{
    if( _foo != null )
    {
        _foo( contract );
    }
}

Затем разработчик теста может, например, решить, что он хочет установить другое значение имени при вызове Refresh.

Foo<Person>( p => p.Name = "New Name" );
01.08.2011

Ответы:


1

У вас есть ковариантность и контравариантность в неправильном направлении. Рассмотрим Action<object> и Action<string>. Удалив настоящие дженерики, вы пытаетесь сделать что-то вроде этого:

private Action<object> _foo;

public void Foo(Action<string> bar)
{
    // This won't compile...
    _foo = bar;
}

Теперь предположим, что мы пишем:

_foo(new Button());

Это нормально, потому что Action<object> можно передать любой объект... но мы инициализировали его делегатом, который должен принимать строковый аргумент. Ой.

Это не безопасно для типов, поэтому не компилируется.

Другой способ будет работать:

private Action<string> _foo;

public void Foo(Action<object> bar)
{
    // This is fine...
    _foo = bar;
}

Теперь, когда мы вызываем _foo, мы должны передать строку, но это нормально, потому что мы инициализировали ее делегатом, который может принимать любую ссылку object в качестве параметра, так что это нормально дать ему строку.

Таким образом, в основном Action<T> контравариантно, тогда как Func<T> ковариантно:

Func<string> bar = ...;
Func<object> foo = bar; // This is fine
object x = foo(); // This is guaranteed to be okay

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

01.08.2011
  • @MDeSchaepmeester: Нет, не я - в рабочем коде (вторая половина поста) я присваиваю значение Action<object> (bar) переменной Action<string> (_foo), а затем присваиваю значение Func<string> (bar) Func<object> переменная (foo). 20.05.2016
  • Вы правы, глупая ошибка, я на самом деле не читал этот код и предположил, что вы вызываете Foo с _foo в качестве параметра (я пришел сюда, потому что делал это) 23.05.2016

  • 2

    Его нельзя присвоить, потому что, поскольку вы используете контравариант вместо коварианта, нет способа гарантировать, что общий тип может быть присвоен foo.

    01.08.2011
  • Ну, на самом деле это не потому, что [ОП] использует дженерики, а потому, что Action<T> является контравариантным, а не ковариантным. 02.08.2011
  • @ Джон Скит, ты прав, я плохо объяснил, я отредактирую. 02.08.2011
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..