C# объекты вне области не собираются - PullRequest
1 голос
/ 09 января 2020

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

public class Customer
{
    public Customer()
    {
        ++InstanceCount;
    }

    public Customer(int customerId)
    {
        CustomerId = customerId;
        ++InstanceCount;
    }

    ~Customer()
    {
        --InstanceCount;
    }

    public static int InstanceCount { get; private set; }
}
[TestClass]
public class CustomerTest
    {
    [TestMethod]
    public void FullNameLastNameEmpty()
    {
        //--Arrange
        Customer customer = new Customer {FirstName = "John"};
        const string expected = "John";

        //--Act
        string actual = customer.FullName;

        //--Assert
        Assert.AreEqual(expected, actual);
    }

    [TestMethod]
    public void StaticTest()
    {
        //--Arrange
        Customer[] customers =
        {
            new Customer {FirstName = "John"},
            new Customer {FirstName = "Jane"},
            new Customer {FirstName = "Jeff"} 
        };

        //--Act

        //--Assert
        Assert.AreEqual(3, Customer.InstanceCount);
    }
}

Я нигде не передаю ссылку, поэтому клиент должен быть освобожден, но это не так, я даже пытался вызвать G C .Collect () в начале StaticTest (), чтобы вызвать вызовы деструктора, но все же не повезло.

Может кто-нибудь объяснить, почему это происходит и как это исправить.

Ответы [ 3 ]

6 голосов
/ 09 января 2020

C# не является C ++. Деструкторы, или, вернее, финализаторы, являются функцией для очень специфических c целей, а именно для обработки удаления неуправляемых ресурсов, содержащихся в ваших объектах. Наполнение лога программы c в финализаторах - одна из немногих очень плохих идей, которые вы можете иметь с C#. Почему? Вот краткое изложение:

  • Нет гарантии, когда объект будет собран, когда он выходит из области видимости.
  • Черт, нет гарантии, что коллекция когда-либо произойдет, если нет никакого давления памяти.
  • Нет никакой гарантии относительно порядка, в котором запускается финализатор объекта.
  • Нет никакой гарантии относительно того, когда запускается финализатор объекта - может быть сразу после того, как он выходит из области видимости, может через минуту.
  • Черт, нет гарантии, что финализатор когда-либо запустится.
  • Нет гарантии, что, если он запустится, он запустится после того, как завершится ваш ctor. Таким образом, в вашем случае вы можете уменьшить число экземпляров, не увеличивая его. Конечно, это вряд ли произойдет, но это может произойти.
  • О, это также отрицательно влияет на производительность всего вашего приложения, но это довольно незначительно по сравнению со злом выше.

Обычно при использовании финализаторов все, что вы знаете, неверно . Финализаторы настолько злы, что есть часть вторая этого!

Принцип использования финализаторов - не . Даже если вы опытный C# разработчик, шансы на то, что вам на самом деле нужен финализатор, очень малы, в то время как шансы на то, что вы получите что-то не так при написании этого, огромны . Если вы вынуждены работать с неуправляемыми ресурсами, следуйте рекомендациям и используйте SafeHandle.

Если вам требуется подсчитать количество экземпляров, лучше всего использовать IDisposable шаблон . Это совместный способ сделать это, означающий, что вызывающий код должен будет на самом деле Dispose() вашего объекта для уменьшения числа. Но это не может быть по-другому. Суть в том, что в C# нет надежного способа что-то сделать, когда последняя ссылка на объект выходит из области видимости .

ОБНОВЛЕНИЕ:

Я надеюсь, что я отговорил вас от написания финализаторов. Серьезно, если вы не профессионал и точно не знаете, что вам нужен финализатор и можете описать все, что происходит на уровне CLR, просто не делайте этого. Однако . Есть отвратительный взлом, который вы можете использовать, чтобы получить то, что вы хотите. Эти два заклинания:

GC.Collect();
GC.WaitForPendingFinalizers();

должны заставить коллекцию произойти, которая поместит ваши объекты в очередь финализатора и подождет, пока все они завершатся sh. Если вы выполняете это после каждого теста, это должно дать вам ожидаемое поведение. Но, пожалуйста, ради нас всех, не используйте это в серьезном коде. Я только что показал вам ужасно острый обоюдоострый клинок. Без какой-либо ручки. Это в огне. Этот второй метод даже не гарантированно завершится.

1 голос
/ 09 января 2020

кто-то еще уже упомянул разницу между деструктором c ++ и финализатором c#, поэтому я не собираюсь повторять это.

В случае, если вам интересно, почему ваш тест не удался даже после вызова G C: ваш Переменная никогда не выходила за рамки. Массив customers содержит ссылки на все экземпляры 3 Customer, а область действия массива customers - функция StaticTest. Это будет выходить за рамки только после возврата StaticTest.

class Program
{
    static void Create()
    {
        Customer[] customers =
        {
            new Customer (),
            new Customer (),
            new Customer (),
        };
    }

    static void Main(string[] args)
    {
        Create();
        Console.WriteLine($"Before GC Count: {Customer.InstanceCount}");

        GC.Collect(0);
        GC.WaitForPendingFinalizers();

        Console.WriteLine($"After GC Count: {Customer.InstanceCount}");
    }
}

Это даст ожидаемый результат

До G C Количество: 3
После G C Количество: 0

0 голосов
/ 09 января 2020

добавляя ко всем этим ответам, ниже приведены мои два цента.

Если его c#,

  1. , попробуйте добавить IDispoable в ваш класс и создать / переопределить Метод dispose () в вашем классе. Затем обработайте логарифм декремента c.

  2. попробуйте использовать метод Test Teardown / finalize и методы Test initialize, чтобы соответствующим образом использовать удаление объектов после завершения каждого тестового случая.

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

...