source

개체를 삭제하고 null로 설정해야 합니까?

factcode 2023. 5. 9. 23:02
반응형

개체를 삭제하고 null로 설정해야 합니까?

개체를 폐기하고 null로 설정해야 합니까? 아니면 범위를 벗어나면 가비지 수집기에서 해당 개체를 정리합니까?

개체는 더 이상 사용되지 않고 가비지 수집기가 적합하다고 판단될 때 정리됩니다.를 때로다같개설할정수있야습다니도해체로 설정해야 할 .null: 더 이 필요 ), 으로 "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""null.

물건을 처분하는 것과 관련하여, 저는 @Andre에 동의합니다.대상이 다음과 같은 경우IDisposable개체가 관리되지 않는 리소스를 사용하는 경우 특히 더 이상 필요하지 않을 때 이를 폐기하는 것이 좋습니다.관리되지 않는 리소스를 삭제하지 않으면 메모리 누수가 발생합니다.

당신은 할 수 .usingusing진술.

using (MyIDisposableObject obj = new MyIDisposableObject())
{
    // use the object here
} // the object is disposed here

기능적으로 다음과 같습니다.

MyIDisposableObject obj;
try
{
    obj = new MyIDisposableObject();
}
finally
{
    if (obj != null)
    {
        ((IDisposable)obj).Dispose();
    }
}

객체는 C++에서처럼 C#에서 범위를 벗어나지 않습니다.더 이상 사용하지 않을 때는 가비지 수집기에서 자동으로 처리합니다.이것은 변수의 범위가 완전히 결정론적인 C++보다 더 복잡한 접근법입니다.CLR 가비지 수집기는 생성된 모든 개체를 능동적으로 검토하고 사용 중인지 여부를 확인합니다.

객체는 하나의 함수에서 "범위 밖"으로 이동할 수 있지만, 그 값이 반환되면 GC는 호출 함수가 반환 값을 유지하는지 여부를 확인합니다.

를 " " " " 로 합니다.null가비지 수집은 다른 개체가 참조하는 개체를 파악하여 작동하므로 불필요합니다.

실제로는 파괴에 대해 걱정할 필요가 없고, 그저 효과가 있고 훌륭합니다 :)

Dispose는 (으)ㄹ 수 없는 것을 구현하는 .IDisposable당신이 그들과 함께 일하는 것이 끝나면.은 일적으사다다니용합을음로를 합니다.using다음과 같은 개체로 차단합니다.

using (var ms = new MemoryStream()) {
  //...
}

EDIT On 변수 범위입니다.Craig는 변수 범위가 개체 수명에 영향을 미치는지 여부를 물었습니다.CLR의 그 측면을 제대로 설명하기 위해서는 C++과 C#의 몇 가지 개념을 설명해야 할 것입니다.

실가변범위

두 언어 모두에서 변수는 정의된 것과 동일한 범위(클래스, 함수 또는 대괄호로 둘러싸인 문 블록)에서만 사용할 수 있습니다.그러나 미묘한 차이는 C#에서 변수를 내포된 블럭에서 재정의할 수 없다는 것입니다.

C++에서 이것은 완벽하게 합법적입니다.

int iVal = 8;
//iVal == 8
if (iVal == 8){
    int iVal = 5;
    //iVal == 5
}
//iVal == 8

그러나 C#에서는 컴파일러 오류가 발생합니다.

int iVal = 8;
if(iVal == 8) {
    int iVal = 5; //error CS0136: A local variable named 'iVal' cannot be declared in this scope because it would give a different meaning to 'iVal', which is already used in a 'parent or current' scope to denote something else
}

생성된 MSIL을 보면 이는 타당합니다. 함수에서 사용되는 모든 변수는 함수의 시작 부분에 정의됩니다.다음 기능을 살펴봅니다.

public static void Scope() {
    int iVal = 8;
    if(iVal == 8) {
        int iVal2 = 5;
    }
}

아래는 생성된 IL입니다.if 블록 내부에 정의된 iVal2는 실제로 기능 수준에서 정의됩니다.사실상 이것은 C#이 변수 수명에 관한 한 클래스 및 함수 수준 범위만 가지고 있다는 것을 의미합니다.

.method public hidebysig static void  Scope() cil managed
{
  // Code size       19 (0x13)
  .maxstack  2
  .locals init ([0] int32 iVal,
           [1] int32 iVal2,
           [2] bool CS$4$0000)

//Function IL - omitted
} // end of method Test2::Scope

C++ 범위 및 개체 수명

스택에 할당된 C++ 변수가 범위를 벗어날 때마다 소멸됩니다.C++에서는 스택이나 힙에 개체를 생성할 수 있습니다.스택에서 생성할 때 실행이 범위를 벗어나면 스택에서 제거됩니다.

if (true) {
  MyClass stackObj; //created on the stack
  MyClass heapObj = new MyClass(); //created on the heap
  obj.doSomething();
} //<-- stackObj is destroyed
//heapObj still lives

힙에 C++ 개체가 생성되면 명시적으로 제거해야 합니다. 그렇지 않으면 메모리 누수가 됩니다. 그렇지 않으면 메모리 누수입니다.하지만 스택 변수에는 그런 문제가 없습니다.

C# 객체 수명

CLR에서 개체(즉, 참조 유형)는 항상 관리되는 힙에 생성됩니다.이것은 객체 생성 구문에 의해 더욱 강화됩니다.이 코드 조각을 고려하십시오.

MyClass stackObj;

에서는 C++ 에이다인음스만듭다니를스에 .MyClass스택에서 기본 생성자를 호출합니다.에서 클래스 C# ▁c▁to▁a▁reference에 대한 참조를 생성할 것입니다.MyClass그건 아무 것도 가리키지 않습니다.은 클스의인만드유방은법일을 입니다.new연산자:

MyClass stackObj = new MyClass();

어떤 면에서 C# 객체는 다음을 사용하여 생성된 객체와 매우 유사합니다.newC++의 구문 - 힙에서 생성되지만 C++ 개체와는 달리 런타임에 의해 관리되므로 파괴할 염려가 없습니다.

객체가 항상 힙에 있기 때문에 객체 참조(예: 포인터)가 범위를 벗어난다는 사실이 모호해집니다.단순히 개체에 대한 참조가 존재하는 것보다 개체를 수집할지 여부를 결정하는 데 더 많은 요인이 관련됩니다.

C# 객체 참조

Jon Skeet는 Java의 객체 참조를 객체인 풍선에 연결된 문자열 조각과 비교했습니다.C# 객체 참조에도 동일한 유사성이 적용됩니다.개체가 포함된 힙의 위치를 가리키기만 하면 됩니다.따라서 null로 설정해도 개체 수명에 즉각적인 영향을 미치지 않으며 GC가 풍선을 "팝"할 때까지 풍선이 계속 존재합니다.

풍선에 대한 비유를 계속하면, 풍선에 아무런 조건이 붙지 않으면, 풍선이 파괴될 수 있다는 것이 논리적으로 보일 것입니다.실제로 이것이 관리되지 않는 언어에서 참조 카운트된 개체의 작동 방식입니다.이 접근법은 순환 참조에는 잘 작동하지 않습니다.두 개의 풍선이 끈으로 연결되어 있지만 다른 어떤 풍선에도 끈이 없다고 상상해 보세요.단순한 재계산 규칙에 따르면, 풍선 그룹 전체가 "고립" 상태임에도 불구하고 둘 다 계속 존재합니다.

.NET 물체는 지붕 밑에 있는 헬륨 풍선과 매우 비슷합니다.지붕이 열리면(GC가 실행됨) - 함께 묶여 있는 풍선 그룹이 있을 수도 있지만 사용하지 않은 풍선은 떠내려갑니다.

.NET GC는 생성 GC와 표시 및 스위프의 조합을 사용합니다.생성 접근 방식은 가장 최근에 할당된 개체를 검사하는 런타임을 포함합니다. 개체가 사용되지 않을 가능성이 높고 표시 및 스위프에는 전체 개체 그래프를 살펴보고 사용되지 않은 개체 그룹이 있는지 확인하는 런타임이 포함됩니다.이것은 순환 의존성 문제를 적절하게 처리합니다.

또한 .NET GC는 할 일이 많기 때문에 다른 스레드(finalizer 스레드라고 함)에서 실행되며 메인 스레드에서 그렇게 하면 프로그램이 중단됩니다.

다른 사람들이 말했듯이 당신은 분명히 전화하고 싶어합니다.Dispose에서 수이시행면되를 실행하는 IDisposable저는 이것에 대해 상당히 엄격한 입장을 취하고 있습니다.어떤 은 떤어사은그전가화들람을 부른다고 .DisposeDataSet예를 들어, 그들이 그것을 분해하고 그것이 의미 있는 일을 하지 않았다는 것을 보았기 때문에 의미가 없습니다.하지만, 저는 그 주장에는 오류가 많다고 생각합니다.

이 주제에 대한 존경받는 사람들의 흥미로운 토론을 위해 이것을 읽으세요.그럼 제가 왜 제프리 리히터가 잘못된 진영에 있다고 생각하는지 여기서 제 추론을 읽어보세요.

자,이 이제다 대참한설대하여야에 .null대답은 아니오입니다.다음 코드로 제 요점을 설명하겠습니다.

public static void Main()
{
  Object a = new Object();
  Console.WriteLine("object created");
  DoSomething(a);
  Console.WriteLine("object used");
  a = null;
  Console.WriteLine("reference set to null");
}

그래서 당신은 그 물체가 언제 언급되었다고 생각합니까?a수집할 자격이 있습니까?통화 후에 당신이 말했다면a = null그렇다면 당신은 틀렸습니다.만약 당신이 그 후에 말했다면.Main메소드가 완료되면 당신도 틀립니다.정답은 고객과의 통화 에 차량을 회수할 수 있는 자격이 있다는 것입니다.DoSomething.맞다.참조가 다음으로 설정되기 전에 적합합니다.null 리고아마전오기전에가화그도▁to▁call에 전화하기 에도.DoSomething 더 이상 할 수 입니다.JIT 컴파일러는 객체 참조가 여전히 루트에 있더라도 더 이상 참조되지 않을 때 인식할 수 있기 때문입니다.

C#에서는 개체를 null로 설정할 필요가 없습니다.컴파일러와 런타임은 더 이상 범위에 포함되지 않는 경우를 파악합니다.

예, ID를 구현하는 개체는 폐기해야 합니다.

가 객가구는경을 .IDisposable그럼, 당신은 그것을 처분해야 합니다.개체가 네이티브 리소스(파일 핸들, OS 개체)에 매달려 있을 수 있지만 그렇지 않으면 즉시 해제할 수 없습니다.이로 인해 리소스 부족, 파일 잠금 문제 및 기타 미묘한 버그가 발생할 수 있습니다.

MSDN에서 폐기 방법 구현을 참조하십시오.

일반적인 답변에 동의합니다. 예, 폐기해야 합니다. 아니요. 일반적으로 변수를 null로 설정하면 안 됩니다.하지만 폐기는 주로 메모리 관리에 관한 것이 아니라는 점을 지적하고 싶습니다.예, 메모리 관리에 도움이 될 수도 있고 때로는 도움이 될 수도 있습니다. 그러나 주요 목적은 부족한 리소스를 결정적으로 해제하는 것입니다.

예를 들어 하드웨어 포트(예: 직렬), TCP/IP 소켓, 파일(전용 액세스 모드) 또는 데이터베이스 연결을 열면 해당 항목이 릴리스될 때까지 다른 코드가 해당 항목을 사용할 수 없습니다.Dispose는 일반적으로 이러한 항목을 해제합니다(GDI 및 기타 "OS" 핸들 등과 함께 1000개의 사용 가능하지만 전체적으로 여전히 제한되어 있음).소유자 개체에 대한 dipose를 호출하지 않고 이러한 리소스를 명시적으로 해제하지 않으면 나중에 동일한 리소스를 다시 열려고 시도합니다(또는 다른 프로그램이 열려 있음). 표시되지 않거나 수집되지 않은 개체의 항목이 여전히 열려 있기 때문에 열려는 시도가 실패합니다.물론 GC가 항목을 수집할 때(폐기 패턴이 올바르게 구현된 경우) 리소스가 해제됩니다.하지만 언제가 될지 모르기 때문에 언제 리소스를 다시 여는 것이 안전한지 알 수 없습니다.이것이 Dispose가 해결해야 할 주요 문제입니다.물론 이러한 핸들을 해제하면 메모리도 해제되는 경우가 많습니다. 해제하지 않으면 메모리가 해제되지 않을 수도 있습니다.따라서 메모리 누수 또는 메모리 정리 지연에 대한 모든 이야기가 나옵니다.

저는 이것이 문제를 일으키는 실제 사례를 보았습니다.예를 들어, 나는 ASP를 본 적이 있습니다.sql server '연결 풀이 꽉 찼습니다'로 인해 (짧은 시간 동안 또는 웹 서버 프로세스가 다시 시작될 때까지) 결국 데이터베이스에 연결하지 못하는 넷 웹 애플리케이션...즉, 너무 많은 연결이 생성되고 너무 짧은 시간 내에 명시적으로 릴리스되지 않아 새 연결을 생성할 수 없으며 풀의 많은 연결이 활성화되지는 않았지만 여전히 폐기되지 않았거나 수집되지 않은 개체에서 참조되므로 재사용할 수 없습니다.필요한 경우 데이터베이스 연결을 올바르게 배치하면 이 문제가 발생하지 않습니다(최소한 동시 액세스가 매우 높은 경우는 그렇지 않습니다).

만약 그들이 ID 일회용 인터페이스를 구현한다면, 당신은 그것들을 폐기해야 합니다.나머지는 쓰레기 수거원이 처리할 것입니다.

편집: 사용하는 것이 좋습니다.using일회용 항목으로 작업할 때 명령:

using(var con = new SqlConnection("..")){ ...

항상 폐기를 호출합니다.그것은 위험을 감수할 가치가 없습니다.대규모 관리되는 엔터프라이즈 애플리케이션은 존중되어야 합니다.어떤 추측도 할 수 없습니다. 그렇지 않으면 그것은 당신을 물어뜯기 위해 돌아올 것입니다.

레피의 말을 듣지 마세요.

많은 물체들이 실제로 ID 일회용을 구현하지 않기 때문에 걱정할 필요가 없습니다.만약 그들이 진정으로 범위를 벗어나면 그들은 자동으로 자유로워질 것입니다.또한 저는 무언가를 무효로 설정해야 하는 상황에 직면한 적이 없습니다.

한 가지 일어날 수 있는 일은 많은 물체를 열어둘 수 있다는 것입니다.이렇게 하면 응용 프로그램의 메모리 사용량이 크게 증가할 수 있습니다.때때로 이것이 실제로 메모리 누수인지 아니면 애플리케이션이 많은 작업을 수행하고 있는지 확인하는 것이 어렵습니다.

메모리 프로파일 도구는 그런 것들을 도울 수 있지만, 까다로울 수 있습니다.

또한 필요하지 않은 이벤트는 항상 구독을 취소합니다.또한 WPF 바인딩 및 제어에도 주의해야 합니다.일반적인 상황은 아니지만 WPF 컨트롤이 기본 개체에 바인딩되어 있는 상황을 발견했습니다.기본 개체가 커서 메모리를 많이 차지했습니다.WPF 컨트롤은 새 인스턴스로 교체되고 있었고, 이전 컨트롤은 여전히 어떤 이유에서인지 어슬렁거리고 있었습니다.이로 인해 메모리 누수가 크게 발생했습니다.

뒷 사이트에서는 코드가 제대로 작성되지 않았지만, 중요한 것은 사용되지 않는 것이 범위를 벗어나도록 해야 한다는 것입니다.그것은 메모리 프로파일러로 찾는 데 오랜 시간이 걸렸습니다. 메모리에 어떤 것이 유효하고 어떤 것이 있어서는 안 되는지 알기 어렵기 때문입니다.

객체가 구현되는 경우IDisposable당신은 전화해야 합니다Dispose(또는)Close경우에 따라 Dispose(폐기)가 호출됩니다.

를 일반적으개다설음필없정습다니요가할로으체로를로 설정할 .nullGC는 객체가 더 이상 사용되지 않을 것임을 알 것이기 때문입니다.

를 개를다음설때정한할가있예지습다니외가체로으▁to▁objects▁when다▁i있▁there▁exception습니▁is▁set▁one로 설정할 때 한 가지 예외가 있습니다.null작업해야 하는 많은 개체(데이터베이스에서)를 검색하여 컬렉션(또는 어레이)에 저장하는 경우"작업"이 완료되면 개체를 다음으로 설정합니다.nullGC는 제가 작업을 완료한 것을 모르기 때문입니다.

예:

using (var db = GetDatabase()) {
    // Retrieves array of keys
    var keys = db.GetRecords(mySelection); 

    for(int i = 0; i < keys.Length; i++) {
       var record = db.GetRecord(keys[i]);
       record.DoWork();
       keys[i] = null; // GC can dispose of key now
       // The record had gone out of scope automatically, 
       // and does not need any special treatment
    }
} // end using => db.Dispose is called

일반적으로 필드를 null로 설정할 필요가 없습니다.그러나 관리되지 않는 리소스는 항상 폐기하는 것이 좋습니다.

경험을 바탕으로 다음을 수행하는 것이 좋습니다.

  • 이벤트가 더 이상 필요하지 않으면 이벤트 등록을 취소합니다.
  • 더 이상 필요하지 않은 경우 대리자 또는 식을 보유하는 필드를 null로 설정합니다.

저는 위의 조언을 따르지 않은 직접적인 결과인 매우 어려운 문제들을 발견했습니다.

이 작업을 수행하기에 좋은 위치는 폐기()에 있지만 일반적으로 빠르면 빠를수록 좋습니다.

일반적으로 개체에 대한 참조가 존재하는 경우 GC(가비지 수집기)는 개체가 더 이상 사용되지 않는지 확인하는 데 몇 세대 더 걸릴 수 있습니다.그 동안 개체는 메모리에 남아 있습니다.

앱이 예상보다 훨씬 더 많은 메모리를 사용하고 있다는 것을 알기 전까지는 문제가 없을 수 있습니다.이 경우 정리되지 않는 개체를 확인하기 위해 메모리 프로파일러를 연결합니다.다른 개체를 참조하는 필드를 null로 설정하고 폐기 시 컬렉션을 지우면 GC가 메모리에서 제거할 수 있는 개체를 파악하는 데 도움이 됩니다.GC는 사용된 메모리를 더 빨리 회수하여 앱의 메모리 부족을 훨씬 줄이고 더 빠르게 만듭니다.

저도 대답해야 해요.JIT는 변수 사용에 대한 정적 분석의 코드와 함께 테이블을 생성합니다.이러한 테이블 항목은 현재 스택 프레임의 "GC-Roots"입니다.명령 포인터가 진행됨에 따라 테이블 항목이 잘못되어 가비지 수집 준비가 완료됩니다.따라서:범위가 지정된 변수인 경우에는 null로 설정할 필요가 없습니다. GC가 개체를 수집합니다.멤버 또는 정적 변수인 경우 null로 설정해야 합니다.

파티에 조금 늦었지만 여기서 언급되지 않은 시나리오가 하나 있는데, 클래스 A가 일회용 ID를 구현하고 ID 일회용 객체이기도 한 공공 재산을 공개한다면 클래스 A는 자신의 Dispose 방식으로 생성한 일회용 객체를 폐기하는 것뿐만 아니라,또한 null로 설정합니다.그 이유는 개체를 폐기하는 것과 GCed를 받게 하는 것(더 이상 참조가 없기 때문)이 결코 동일하지 않기 때문입니다. 비록 그런 일이 일어난다면 분명 버그일 것입니다.클래스 A의 클라이언트가 클래스 A 유형의 개체를 폐기하더라도 해당 개체는 여전히 존재합니다.그런 다음 클라이언트가 이러한 공용 속성 중 하나에 액세스하려고 하면(지금도 삭제됨) 결과가 예상치 못할 수 있습니다.삭제 및 무효화된 경우에는 즉시 null 참조 예외가 발생하므로 문제를 진단하기가 더 쉬워집니다.

언급URL : https://stackoverflow.com/questions/2926869/do-you-need-to-dispose-of-objects-and-set-them-to-null

반응형