File: CodeGen\DestructorTests.cs
Web Access
Project: src\src\Compilers\CSharp\Test\Emit\Microsoft.CodeAnalysis.CSharp.Emit.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Emit.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.CSharp.UnitTests.Emit;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen
{
    public class DestructorTests : EmitMetadataTestBase
    {
        [ConditionalFact(typeof(DesktopOnly))]
        public void ClassDestructor()
        {
            var text = @"
using System;
 
public class Base
{
    ~Base()
    {
        Console.WriteLine(""~Base"");
    }
}
 
public class Program
{
    public static void Main()
    {
        Base b = new Base();
        b = null;
        GC.Collect(GC.MaxGeneration);
        GC.WaitForPendingFinalizers();
    }
}
";
            var validator = GetDestructorValidator("Base");
            var compVerifier = CompileAndVerify(text,
                sourceSymbolValidator: validator,
                symbolValidator: validator,
                expectedOutput: @"~Base",
                expectedSignatures: new[]
                {
                    Signature("Base", "Finalize", ".method family hidebysig virtual instance System.Void Finalize() cil managed")
                });
 
            compVerifier.VerifyIL("Base.Finalize", @"
{
  // Code size       20 (0x14)
  .maxstack  1
  .try
  {
    IL_0000:  ldstr      ""~Base""
    IL_0005:  call       ""void System.Console.WriteLine(string)""
    IL_000a:  leave.s    IL_0013
  }
  finally
  {
    IL_000c:  ldarg.0
    IL_000d:  call       ""void object.Finalize()""
    IL_0012:  endfinally
  }
  IL_0013:  ret
}
");
        }
 
        [ConditionalFact(typeof(DesktopOnly))]
        [CompilerTrait(CompilerFeature.ExpressionBody)]
        public void ExpressionBodiedClassDestructor()
        {
            var text = @"
using System;
 
public class Base
{
    ~Base() => Console.WriteLine(""~Base"");
}
 
public class Program
{
    public static void Main()
    {
        Base b = new Base();
        b = null;
        GC.Collect(GC.MaxGeneration);
        GC.WaitForPendingFinalizers();
    }
}
";
            var validator = GetDestructorValidator("Base");
            var compVerifier = CompileAndVerify(text,
                sourceSymbolValidator: validator,
                symbolValidator: validator,
                expectedOutput: @"~Base",
                expectedSignatures: new[]
                {
                    Signature("Base", "Finalize", ".method family hidebysig virtual instance System.Void Finalize() cil managed")
                });
 
            compVerifier.VerifyIL("Base.Finalize", @"
{
  // Code size       20 (0x14)
  .maxstack  1
  .try
  {
    IL_0000:  ldstr      ""~Base""
    IL_0005:  call       ""void System.Console.WriteLine(string)""
    IL_000a:  leave.s    IL_0013
  }
  finally
  {
    IL_000c:  ldarg.0
    IL_000d:  call       ""void object.Finalize()""
    IL_0012:  endfinally
  }
  IL_0013:  ret
}
");
        }
 
        [ConditionalFact(typeof(DesktopOnly))]
        [CompilerTrait(CompilerFeature.ExpressionBody)]
        public void ExpressionBodiedSubClassDestructor()
        {
            var text = @"
using System;
 
public class Base
{
    ~Base() => Console.WriteLine(""~Base"");
}
 
public class Derived : Base
{
    ~Derived() => Console.WriteLine(""~Derived"");
}
 
public class Program
{
    public static void Main()
    {
        Derived d = new Derived();
        d = null;
        GC.Collect(GC.MaxGeneration);
        GC.WaitForPendingFinalizers();
    }
}
";
            var validator = GetDestructorValidator("Derived");
            var compVerifier = CompileAndVerify(text,
                sourceSymbolValidator: validator,
                symbolValidator: validator,
                expectedOutput: @"~Derived
~Base",
                expectedSignatures: new[]
                {
                    Signature("Base", "Finalize", ".method family hidebysig virtual instance System.Void Finalize() cil managed"),
                    Signature("Derived", "Finalize", ".method family hidebysig virtual instance System.Void Finalize() cil managed")
                });
 
            compVerifier.VerifyIL("Base.Finalize", @"
{
  // Code size       20 (0x14)
  .maxstack  1
  .try
  {
    IL_0000:  ldstr      ""~Base""
    IL_0005:  call       ""void System.Console.WriteLine(string)""
    IL_000a:  leave.s    IL_0013
  }
  finally
  {
    IL_000c:  ldarg.0
    IL_000d:  call       ""void object.Finalize()""
    IL_0012:  endfinally
  }
  IL_0013:  ret
}
");
            compVerifier.VerifyIL("Derived.Finalize", @"
{
  // Code size       20 (0x14)
  .maxstack  1
  .try
  {
    IL_0000:  ldstr      ""~Derived""
    IL_0005:  call       ""void System.Console.WriteLine(string)""
    IL_000a:  leave.s    IL_0013
  }
  finally
  {
    IL_000c:  ldarg.0
    IL_000d:  call       ""void Base.Finalize()""
    IL_0012:  endfinally
  }
  IL_0013:  ret
}
");
            compVerifier.VerifyDiagnostics();
        }
 
        [ConditionalFact(typeof(DesktopOnly))]
        public void SubclassDestructor()
        {
            var text = @"
using System;
 
public class Base
{
    ~Base()
    {
        Console.WriteLine(""~Base"");
    }
}
 
public class Derived : Base
{
    ~Derived()
    {
        Console.WriteLine(""~Derived"");
    }
}
 
public class Program
{
    public static void Main()
    {
        Base b = new Derived();
        b = null;
        GC.Collect(GC.MaxGeneration);
        GC.WaitForPendingFinalizers();
    }
}
";
            var validator = GetDestructorValidator("Derived");
            var compVerifier = CompileAndVerify(text,
                sourceSymbolValidator: validator,
                symbolValidator: validator,
                expectedOutput: @"~Derived
~Base",
                expectedSignatures: new[]
                {
                    Signature("Base", "Finalize", ".method family hidebysig virtual instance System.Void Finalize() cil managed"),
                    Signature("Derived", "Finalize", ".method family hidebysig virtual instance System.Void Finalize() cil managed")
                });
 
            compVerifier.VerifyIL("Base.Finalize", @"
{
  // Code size       20 (0x14)
  .maxstack  1
  .try
  {
    IL_0000:  ldstr      ""~Base""
    IL_0005:  call       ""void System.Console.WriteLine(string)""
    IL_000a:  leave.s    IL_0013
  }
  finally
  {
    IL_000c:  ldarg.0
    IL_000d:  call       ""void object.Finalize()""
    IL_0012:  endfinally
  }
  IL_0013:  ret
}
");
            compVerifier.VerifyIL("Derived.Finalize", @"
{
  // Code size       20 (0x14)
  .maxstack  1
  .try
  {
    IL_0000:  ldstr      ""~Derived""
    IL_0005:  call       ""void System.Console.WriteLine(string)""
    IL_000a:  leave.s    IL_0013
  }
  finally
  {
    IL_000c:  ldarg.0
    IL_000d:  call       ""void Base.Finalize()""
    IL_0012:  endfinally
  }
  IL_0013:  ret
}
");
            compVerifier.VerifyDiagnostics();
        }
 
        [ConditionalFact(typeof(WindowsDesktopOnly))]
        public void DestructorOverridesNonDestructor()
        {
            var text = @"
using System;
 
public class Base
{
    protected virtual void Finalize() //NB: does not override Object.Finalize
    {
        Console.WriteLine(""~Base"");
    }
}
 
public class Derived : Base
{
    ~Derived() //NB: in metadata, this will implicitly override Base.Finalize, but explicitly override Object.Finalize
    {
        Console.WriteLine(""~Derived"");
    }
}
 
public class Program
{
    public static void Main()
    {
        Base b = new Base();
        b = null;
        GC.Collect(GC.MaxGeneration);
        GC.WaitForPendingFinalizers();
 
        Derived d = new Derived();
        d = null;
        GC.Collect(GC.MaxGeneration);
        GC.WaitForPendingFinalizers();
    }
}
";
            var expectedOutput = @"
~Derived
~Base
";
 
            // Destructors generated by Roslyn should still be destructors when they are loaded back in - 
            // even in cases where the user creates their own Finalize methods (which is legal, but ill-advised).
            // This may not be the case for metadata from other C# compilers (which will likely not have
            // destructors explicitly override System.Object.Finalize).
            var validator = GetDestructorValidator("Derived");
 
            var compVerifier = CompileAndVerify(text,
                sourceSymbolValidator: validator,
                symbolValidator: validator,
                expectedOutput: expectedOutput,
                expectedSignatures: new[]
                {
                Signature("Base", "Finalize", ".method family hidebysig newslot virtual instance System.Void Finalize() cil managed"),
                    Signature("Derived", "Finalize", ".method family hidebysig virtual instance System.Void Finalize() cil managed")
                });
 
            compVerifier.VerifyDiagnostics(
                // (6,28): warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?
                Diagnostic(ErrorCode.WRN_FinalizeMethod, "Finalize"));
        }
 
        [WorkItem(542828, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542828")]
        [ConditionalFact(typeof(WindowsDesktopOnly))]
        public void BaseTypeHasNonVirtualFinalize()
        {
            var text = @"
using System;
 
public class Base
{
    protected void Finalize() //NB: does not override Object.Finalize
    {
        Console.WriteLine(""~Base"");
    }
}
 
public class Derived : Base
{
    ~Derived() //NB: in metadata, this will override Base.Finalize
    {
        Console.WriteLine(""~Derived"");
    }
}
 
public class Program
{
    public static void Main()
    {
        Base b = new Base();
        b = null;
        GC.Collect(GC.MaxGeneration);
        GC.WaitForPendingFinalizers();
 
        Derived d = new Derived();
        d = null;
        GC.Collect(GC.MaxGeneration);
        GC.WaitForPendingFinalizers();
    }
}
";
            var validator = GetDestructorValidator("Derived");
            var compVerifier = CompileAndVerify(text,
                sourceSymbolValidator: validator,
                symbolValidator: validator,
                expectedOutput: @"~Derived
~Base",
                expectedSignatures: new[]
                {
                Signature("Base", "Finalize", ".method family hidebysig instance System.Void Finalize() cil managed"),
                    Signature("Derived", "Finalize", ".method family hidebysig virtual instance System.Void Finalize() cil managed")
                });
 
            compVerifier.VerifyDiagnostics(
                // (6,20): warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?
                Diagnostic(ErrorCode.WRN_FinalizeMethod, "Finalize"));
        }
 
        [WorkItem(542828, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/542828")]
        [ConditionalFact(typeof(WindowsDesktopOnly))]
        public void GenericBaseTypeHasNonVirtualFinalize()
        {
            var text = @"
using System;
 
public class Base<T>
{
    protected void Finalize() //NB: does not override Object.Finalize
    {
        Console.WriteLine(""~Base"");
    }
}
 
public class Derived : Base<int>
{
    ~Derived() //NB: in metadata, this will override Base.Finalize
    {
        Console.WriteLine(""~Derived"");
    }
}
 
public class Program
{
    public static void Main()
    {
        Base<char> b = new Base<char>();
        b = null;
        GC.Collect(GC.MaxGeneration);
        GC.WaitForPendingFinalizers();
 
        Derived d = new Derived();
        d = null;
        GC.Collect(GC.MaxGeneration);
        GC.WaitForPendingFinalizers();
    }
}
";
 
            var validator = GetDestructorValidator("Derived");
            var compVerifier = CompileAndVerify(text,
                sourceSymbolValidator: validator,
                symbolValidator: validator,
                expectedOutput: @"~Derived
~Base",
                expectedSignatures: new[]
                {
                Signature("Base`1", "Finalize", ".method family hidebysig instance System.Void Finalize() cil managed"),
                    Signature("Derived", "Finalize", ".method family hidebysig virtual instance System.Void Finalize() cil managed")
                });
 
            compVerifier.VerifyDiagnostics(
                // (6,20): warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?
                Diagnostic(ErrorCode.WRN_FinalizeMethod, "Finalize"));
        }
 
        [Fact]
        public void StructAndInterfaceHasNonVirtualFinalize()
        {
            var text = @"
public interface I
{
    void Finalize();
}
 
public struct S
{
    void Finalize() { }
}
 
class C
{
    private string Finalize;
}
";
            var compVerifier = CompileAndVerify(text);
 
            compVerifier.VerifyDiagnostics(
                // (4,10): warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?
                //     void Finalize();
                Diagnostic(ErrorCode.WRN_FinalizeMethod, "Finalize"),
                // (9,10): warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?
                //     void Finalize() { }
                Diagnostic(ErrorCode.WRN_FinalizeMethod, "Finalize"),
                // (14,20): warning CS0169: The field 'C.Finalize' is never used
                //     private string Finalize;
                Diagnostic(ErrorCode.WRN_UnreferencedField, "Finalize").WithArguments("C.Finalize")
                );
        }
 
        [Fact]
        public void IsRuntimeFinalizer1()
        {
            var text = @"
public class A
{
    ~A() { }
}
 
public class B
{
    public virtual void Finalize() { }
}
 
public class C : A
{
    public void Finalize() { }
}
 
public class D : B
{
    public override void Finalize() { }
}
 
public struct E
{
    public void Finalize() { }
}
 
public interface F
{
    void Finalize();
}
 
public class G
{
    protected virtual void Finalize() { }
}
 
public class H : G
{
    ~H() { }
}
 
public class I
{
    protected virtual void Finalize<T>() { }
}
 
public class J<T>
{
    protected virtual void Finalize() { }
}
 
public class K<T>
{
    ~K() { }
}
 
public class L<T>
{
	~L() { }
}
 
public class M<T> : L<T>
{
	~M() { }
}
";
            Action<ModuleSymbol> validator = module =>
            {
                var globalNamespace = module.GlobalNamespace;
 
                var mscorlib = module.ContainingAssembly.CorLibrary;
                var systemNamespace = mscorlib.GlobalNamespace.GetMember<NamespaceSymbol>("System");
                Assert.True(systemNamespace.GetMember<NamedTypeSymbol>("Object").GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
 
                Assert.True(globalNamespace.GetMember<NamedTypeSymbol>("A").GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
                Assert.False(globalNamespace.GetMember<NamedTypeSymbol>("B").GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
                Assert.False(globalNamespace.GetMember<NamedTypeSymbol>("C").GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
                Assert.False(globalNamespace.GetMember<NamedTypeSymbol>("D").GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
                Assert.False(globalNamespace.GetMember<NamedTypeSymbol>("E").GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
                Assert.False(globalNamespace.GetMember<NamedTypeSymbol>("F").GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
                Assert.False(globalNamespace.GetMember<NamedTypeSymbol>("G").GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
                Assert.True(globalNamespace.GetMember<NamedTypeSymbol>("H").GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
                Assert.False(globalNamespace.GetMember<NamedTypeSymbol>("I").GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
 
                var intType = systemNamespace.GetMember<TypeSymbol>("Int32");
                Assert.Equal(SpecialType.System_Int32, intType.SpecialType);
 
                var classJ = globalNamespace.GetMember<NamedTypeSymbol>("J");
                Assert.False(classJ.GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
                var classJInt = classJ.Construct(intType);
                Assert.False(classJInt.GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
 
                var classK = globalNamespace.GetMember<NamedTypeSymbol>("K");
                Assert.True(classK.GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
                var classKInt = classK.Construct(intType);
                Assert.True(classKInt.GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
 
                var classL = globalNamespace.GetMember<NamedTypeSymbol>("L");
                Assert.True(classL.GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
                var classLInt = classL.Construct(intType);
                Assert.True(classLInt.GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
 
                var classM = globalNamespace.GetMember<NamedTypeSymbol>("M");
                Assert.True(classM.GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
                var classMInt = classM.Construct(intType);
                Assert.True(classMInt.GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer());
            };
 
            CompileAndVerify(text, sourceSymbolValidator: validator, symbolValidator: validator);
        }
 
        [Fact]
        public void IsRuntimeFinalizer2()
        {
            var text = @"
.class public auto ansi beforefieldinit C
       extends [mscorlib]System.Object
{
  .method family hidebysig virtual instance void 
          Finalize() cil managed
  {
    ret
  }
 
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  }
 
} // end of class C
 
.class public auto ansi beforefieldinit D
       extends [mscorlib]System.Object
{
  .method family hidebysig newslot virtual instance void 
          Finalize() cil managed
  {
    ret
  }
 
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    ldarg.0
    call       instance void [mscorlib]System.Object::.ctor()
    ret
  }
 
} // end of class D
";
            var compilation = CreateCompilationWithILAndMscorlib40("", text);
 
            var globalNamespace = compilation.GlobalNamespace;
 
            Assert.True(globalNamespace.GetMember<NamedTypeSymbol>("C").GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer()); //override of object.Finalize
            Assert.False(globalNamespace.GetMember<NamedTypeSymbol>("D").GetMember<MethodSymbol>("Finalize").IsRuntimeFinalizer()); //same but has "newslot"
        }
 
        [WorkItem(528903, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/528903")] // Won't fix - test just captures behavior.
        [Fact]
        public void DestructorOverridesPublicFinalize()
        {
            var text = @"
public class A
{
    public virtual void Finalize() { }
}
 
public class B : A
{
    ~B() { }
}
";
            var compilation = CreateCompilation(text, options: TestOptions.ReleaseDll);
 
            // NOTE: has warnings, but not errors.
            compilation.VerifyDiagnostics(
                // (4,25): warning CS0465: Introducing a 'Finalize' method can interfere with destructor invocation. Did you intend to declare a destructor?
                //     public virtual void Finalize() { }
                Diagnostic(ErrorCode.WRN_FinalizeMethod, "Finalize"));
 
            // We produce unverifiable code here as per bug resolution (compat concerns, not common case).
            CompileAndVerify(compilation, verify: Verification.FailsPEVerify).VerifyIL("B.Finalize",
 
                @"
{
  // Code size       10 (0xa)
  .maxstack  1
  .try
  {
    IL_0000:  leave.s    IL_0009
  }
  finally
  {
    IL_0002:  ldarg.0
    IL_0003:  call       ""void object.Finalize()""
    IL_0008:  endfinally
  }
  IL_0009:  ret
}
");
        }
 
        [WorkItem(528907, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/528907")]
        [Fact]
        public void BaseTypeHasGenericFinalize()
        {
            var text = @"
public class A
{
    protected void Finalize<T>() { }
}
 
public class B : A
{
    ~B() { }
}
";
            // NOTE: calling object.Finalize, since A.Finalize has the wrong arity.
            // (Dev11 called A.Finalize and failed at runtime, since it wasn't providing
            // a type argument.)
            CompileAndVerify(text).VerifyIL("B.Finalize", @"
{
  // Code size       10 (0xa)
  .maxstack  1
  .try
  {
    IL_0000:  leave.s    IL_0009
  }
  finally
  {
    IL_0002:  ldarg.0
    IL_0003:  call       ""void object.Finalize()""
    IL_0008:  endfinally
  }
  IL_0009:  ret
}
");
        }
 
        [WorkItem(528903, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/528903")]
        [Fact]
        public void MethodImplEntry()
        {
            var text = @"
public class A
{
    ~A() { }
}
";
            CompileAndVerify(text, assemblyValidator: (assembly) =>
            {
                var peFileReader = assembly.GetMetadataReader();
 
                // Find the handle and row for A.
                var pairA = peFileReader.TypeDefinitions.AsEnumerable().
                    Select(handle => new { handle = handle, row = peFileReader.GetTypeDefinition(handle) }).
                    Single(pair => peFileReader.GetString(pair.row.Name) == "A" &&
                        string.IsNullOrEmpty(peFileReader.GetString(pair.row.Namespace)));
                TypeDefinitionHandle handleA = pairA.handle;
                TypeDefinition typeA = pairA.row;
 
                // Find the handle for A's destructor.
                MethodDefinitionHandle handleDestructorA = typeA.GetMethods().AsEnumerable().
                    Single(handle => peFileReader.GetString(peFileReader.GetMethodDefinition(handle).Name) == WellKnownMemberNames.DestructorName);
 
                // Find the handle for System.Object.
                TypeReferenceHandle handleObject = peFileReader.TypeReferences.AsEnumerable().
                    Select(handle => new { handle = handle, row = peFileReader.GetTypeReference(handle) }).
                    Single(pair => peFileReader.GetString(pair.row.Name) == "Object" &&
                        peFileReader.GetString(pair.row.Namespace) == "System").handle;
 
                // Find the handle for System.Object's destructor.
                MemberReferenceHandle handleDestructorObject = peFileReader.MemberReferences.AsEnumerable().
                    Select(handle => new { handle = handle, row = peFileReader.GetMemberReference(handle) }).
                    Single(pair => pair.row.Parent == (EntityHandle)handleObject &&
                        peFileReader.GetString(pair.row.Name) == WellKnownMemberNames.DestructorName).handle;
 
                // Find the MethodImpl row for A.
                MethodImplementation methodImpl = typeA.GetMethodImplementations().AsEnumerable().
                    Select(handle => peFileReader.GetMethodImplementation(handle)).
                    Single();
 
                // The Class column should point to A.
                Assert.Equal(handleA, methodImpl.Type);
 
                // The MethodDeclaration column should point to System.Object.Finalize.
                Assert.Equal((EntityHandle)handleDestructorObject, methodImpl.MethodDeclaration);
 
                // The MethodDeclarationColumn should point to A's destructor.
                Assert.Equal((EntityHandle)handleDestructorA, methodImpl.MethodBody);
            });
        }
 
        private static Action<ModuleSymbol> GetDestructorValidator(string typeName)
        {
            return module => ValidateDestructor(module, typeName);
        }
 
        // NOTE: assumes there's a destructor.
        private static void ValidateDestructor(ModuleSymbol module, string typeName)
        {
            var @class = module.GlobalNamespace.GetMember<NamedTypeSymbol>(typeName);
            var destructor = @class.GetMember<MethodSymbol>(WellKnownMemberNames.DestructorName);
 
            Assert.Equal(MethodKind.Destructor, destructor.MethodKind);
 
            Assert.True(destructor.IsMetadataVirtual());
            Assert.False(destructor.IsVirtual);
            Assert.False(destructor.IsOverride);
            Assert.False(destructor.IsSealed);
            Assert.False(destructor.IsStatic);
            Assert.False(destructor.IsAbstract);
            Assert.Null(destructor.OverriddenMethod);
 
            Assert.Equal(SpecialType.System_Void, destructor.ReturnType.SpecialType);
            Assert.Equal(0, destructor.Parameters.Length);
            Assert.Equal(0, destructor.TypeParameters.Length);
 
            Assert.Equal(Accessibility.Protected, destructor.DeclaredAccessibility);
        }
    }
}