Виртуальный член вызова в конструкторе

Проверьте EJB 3.1 @Asynchronous methods. Это именно то, для чего они предназначены.

Небольшой пример использования OpenEJB 4.0.0-SNAPSHOT. Здесь у нас есть @Singleton компонент с одним методом, отмеченным @Asynchronous. Каждый раз, когда этот метод вызывается кем-либо, в этом случае ваш управляемый bean-компонент JSF, он немедленно возвращается независимо от того, сколько времени действительно принимает метод.

@Singleton
public class JobProcessor {

    @Asynchronous
    @Lock(READ)
    @AccessTimeout(-1)
    public Future addJob(String jobName) {

        // Pretend this job takes a while
        doSomeHeavyLifting();

        // Return our result
        return new AsyncResult(jobName);
    }

    private void doSomeHeavyLifting() {
        try {
            Thread.sleep(SECONDS.toMillis(10));
        } catch (InterruptedException e) {
            Thread.interrupted();
            throw new IllegalStateException(e);
        }
    }
}

Вот небольшой тестовый файл, который вызывает @Asynchronous метод несколько раз подряд.

Каждый вызов возвращает объект Future , который по существу запускает empty и позже будет иметь свое значение, заполненное контейнером, когда вызов соответствующего метода фактически завершается

import javax.ejb.embeddable.EJBContainer;
import javax.naming.Context;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class JobProcessorTest extends TestCase {

    public void test() throws Exception {

        final Context context = EJBContainer.createEJBContainer().getContext();

        final JobProcessor processor = (JobProcessor) context.lookup("java:global/async-methods/JobProcessor");

        final long start = System.nanoTime();

        // Queue up a bunch of work
        final Future red = processor.addJob("red");
        final Future orange = processor.addJob("orange");
        final Future yellow = processor.addJob("yellow");
        final Future green = processor.addJob("green");
        final Future blue = processor.addJob("blue");
        final Future violet = processor.addJob("violet");

        // Wait for the result -- 1 minute worth of work
        assertEquals("blue", blue.get());
        assertEquals("orange", orange.get());
        assertEquals("green", green.get());
        assertEquals("red", red.get());
        assertEquals("yellow", yellow.get());
        assertEquals("violet", violet.get());

        // How long did it take?
        final long total = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start);

        // Execution should be around 9 - 21 seconds
        assertTrue("" + total, total > 9);
        assertTrue("" + total, total < 21);
    }
}

Пример исходного кода

Под обложками, что делает эту работу, является:

  • JobProcessor, вызывающий не видит на самом деле экземпляр JobProcessor. Скорее это подкласс или прокси-сервер, у которого все методы переопределены. Методы, которые должны быть асинхронными, обрабатываются по-разному.
  • Вызовы асинхронного метода просто приводят к созданию Runnable, которое обертывает метод и параметры, которые вы указали. Эта runnable предоставляется Executor , которая является просто рабочей очередью, прикрепленной к пулу потоков.
  • После добавления работы в очередь проксированная версия метода возвращает реализацию из Future, который связан с Runnable, который теперь ожидает в очереди.
  • Когда Runnable, наконец, выполняет метод на экземпляре real JobProcessor , он примет возвращаемое значение и установит его в Future, сделав его доступным для вызывающего.

Важно отметить, что объект AsyncResult, возвращаемый JobProcessor, не является тот же объект Future, который удерживает вызывающий объект. Было бы здорово, если бы реальный JobProcessor мог просто вернуться String, а версия JobProcessor вызывающего абонента могла вернуться Future, но мы не видели никакого способа сделать это, не добавляя дополнительной сложности. Таким образом, AsyncResult представляет собой простой объект-оболочку. Контейнер вытащит String, выбросит AsyncResult, а затем String в real Future, который удерживает вызывающий абонент.

To получите прогресс на этом пути, просто передайте потокобезопасный объект, например AtomicInteger , к методу @Asynchronous и периодически обновляйте его компонентом с кодом процента.

1237
задан CodeNotFound 27 April 2018 в 08:52
поделиться

10 ответов

То, когда объект, записанный в C#, создается, что происходит, - то, что инициализаторы работают в порядке от большей части производного класса до базового класса, и затем конструкторы, выполненные в порядке от базового класса до большей части производного класса (, видят блог Eric Lippert для деталей относительно того, почему это ).

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

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

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

1110
ответ дан Chris Ballance 27 April 2018 в 08:52
поделиться

Существует различие между C++ и C# в этом конкретном случае. В C++ не инициализируется объект, и поэтому небезопасно вызвать virutal функцию в конструкторе. В C#, когда объект класса создается, все его участники являются инициализированным нулем. Возможно вызвать виртуальную функцию в конструкторе, но если Вы будете, мог бы получить доступ к участникам, которые являются все еще нулевыми. Если Вы не должны получать доступ к участникам, довольно безопасно вызвать виртуальную функцию в C#.

1
ответ дан Yuval Peled 27 April 2018 в 08:52
поделиться

Поскольку, пока конструктор не завершил выполнение, объект не полностью инстанцируют. Любые участники, на которых ссылается виртуальная функция, не могут быть инициализированы. В C++, когда Вы находитесь в конструкторе, this только, относится к статическому типу конструктора, Вы находитесь в, а не фактический динамический тип объекта, который создается. Это означает, что вызов виртуальной функции даже не мог бы пойти, где Вы ожидаете его к.

5
ответ дан 1800 INFORMATION 27 April 2018 в 08:52
поделиться

Да, это вообще плохо для вызова виртуального метода в конструкторе.

На данном этапе objet еще не может быть полностью создан, и инварианты, ожидаемые методами еще, не могут содержать.

6
ответ дан David Pierre 27 April 2018 в 08:52
поделиться

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

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

17
ответ дан Alex Lyman 27 April 2018 в 08:52
поделиться

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

  class B
  {
    protected virtual void Foo() { }
  }

  class A : B
  {
    public A()
    {
      Foo(); // warning here
    }
  }

можно изолировать класс A:

  sealed class A : B
  {
    public A()
    {
      Foo(); // no warning
    }
  }

Или можно изолировать метод Foo:

  class A : B
  {
    public A()
    {
      Foo(); // no warning
    }

    protected sealed override void Foo()
    {
      base.Foo();
    }
  }
84
ответ дан Ilya Ryzhenkov 27 April 2018 в 08:52
поделиться

Правила C# очень отличаются от того из Java и C++.

, Когда Вы находитесь в конструкторе для некоторого объекта в C#, тот объект существует в полностью инициализированный (просто не "созданный") форма как ее полностью производный тип.

namespace Demo
{
    class A 
    {
      public A()
      {
        System.Console.WriteLine("This is a {0},", this.GetType());
      }
    }

    class B : A
    {      
    }

    // . . .

    B b = new B(); // Output: "This is a Demo.B"
}

Это означает, что, если Вы вызываете виртуальную функцию от конструктора A, это решит к любому переопределению в B, если Вам предоставят.

Даже при намеренной установке A и B как это, полностью понимая поведение системы, Вы могли быть в для шока позже. Скажите, что Вы вызвали виртуальные функции в конструкторе B, "зная", что они будут обработаны B или как соответствующие. Тогда передачи времени, и кто-то еще решает, что они должны определить C и переопределить некоторые виртуальные функции там. Внезапно конструктор B заканчивает код вызова в C, который мог привести к довольно удивительному поведению.

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

157
ответ дан André Hauptfleisch 27 April 2018 в 08:52
поделиться

Для ответа на вопрос рассмотрите этот вопрос: что будет ниже кода, распечатывают, когда эти Child объект инстанцируют?

class Parent
{
    public Parent()
    {
        DoSomething();
    }

    protected virtual void DoSomething() 
    {
    }
}

class Child : Parent
{
    private string foo;

    public Child() 
    { 
        foo = "HELLO"; 
    }

    protected override void DoSomething()
    {
        Console.WriteLine(foo.ToLower()); //NullReferenceException!?!
    }
}

ответ - то, что на самом деле NullReferenceException будет брошен, потому что foo является пустым. основного конструктора объекта вызывают перед его собственным конструктором . При наличии virtual вызов в конструкторе объекта Вы представляете возможность, что наследование объектов выполнит код, прежде чем они были полностью инициализированы.

587
ответ дан Matt Howells 27 April 2018 в 08:52
поделиться

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

Однако, если Ваш дизайн удовлетворяет принцип замены Лисков, никакой вред не будет причинен. Вероятно, вот почему это терпело - предупреждение, не ошибка.

5
ответ дан xtofl 27 April 2018 в 08:52
поделиться

Я думаю, что игнорирование предупреждения могло бы быть законным, если Вы хотите дать дочернему классу способность установить или переопределить свойство, которое родительский конструктор будет использовать сразу же:

internal class Parent
{
    public Parent()
    {
        Console.WriteLine("Parent ctor");
        Console.WriteLine(Something);
    }

    protected virtual string Something { get; } = "Parent";
}

internal class Child : Parent
{
    public Child()
    {
        Console.WriteLine("Child ctor");
        Console.WriteLine(Something);
    }

    protected override string Something { get; } = "Child";
}

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

Мой вариант использования - то, что я хочу, чтобы дочерний класс обеспечил определенное значение или служебный класс, такой как преобразователь, и я не хочу должным быть называть метод инициализации на основе.

вывод вышеупомянутого при инстанцировании дочернего класса:

Parent ctor
Child
Child ctor
Child
0
ответ дан 19 December 2019 в 20:14
поделиться
Другие вопросы по тегам:

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