简体   繁体   中英

C#: How to declare multiple similar structs?

My app uses several "handles" (all IntPtr's) of varying "type".

I want to help ensure that the correct handle type are passed to my various methods... if I used IntPtr for them all then there's no hint that the method takes handle type A or handle type B.

If L were in C++ land I could use typedefs:

typedef uint32 HdlA;
typedef uint32 HdlB;

and now I have two types, HdlA and HdlB, both of which are generic U32's under the hood. In C++ I could also use a macro to declare the structs.

Additionally, for marshaling reasons, I need these to be value types... can't use a class (naturally, if I could use class that would solve everything).

All handles have essentially identical definitions:

public struct HdlA
{
    private IntPtr _h;
    public bool IsValid             get { return (_h != IntPtr.Zero);} }
    //public HdlA()                 { _h = IntPtr.Zero; } // C# disallows arg-less ctor on struct
    public HdlA(IntPtr h)           { _h = h; }
    public void Invalidate()        { _h = IntPtr.Zero; }
    public static implicit operator IntPtr(HdlA hdl)  { return hdl._h; }
}
public struct HdlB
{
    private IntPtr _h;
    public bool IsValid             get { return (_h != IntPtr.Zero);} }
    //public HdlB()                 { _h = IntPtr.Zero; } // C# disallows arg-less ctor on struct
    public HdlB(IntPtr h)           { _h = h; }
    public void Invalidate()        { _h = IntPtr.Zero; }
    public static implicit operator IntPtr(HdlB hdl)  { return hdl._h; }
}
... etc ...

I certainly can do this longhand - I can declare 5 or 6 identical blocks of code where only the struct names vary - but that's not very elegant.

I've considered using an Interface, but that disallows member fields, so no luck there.

What I'd love is to have a base struct then have HdlA, HdlB, etc simply derive off the base. But C# disallows base types in structs.

This seems like something that should have an easy and elegant solution, but it's escaping me :(

Any ideas?

One solution is to use only one struct with all common fields and add a field(int or Enum) that shows the type of the struct you want to use. Of course if you were able to use classes you would have used Inheritance but in this scenario adding a HandleType field may solve the problem.

You can then check that field in each method to see the right struct has been passed or not.

You don't provide a great deal of context in your question, so I'm stabbing wildly in the dark here.

It might be possible to use classes in your situation, after all. Declare the abstract base class, derive HndlA and HndlB from it, and provide a ToHandle() method on the base class that converts the class to a struct. The value from ToHandle() would be the object you marshal.

Again, just stabbing wildly in the dark.

In C++ I could also use a macro to declare the structs.

In C# you can use a T4 template .

Or strictly, it's not C# itself, though it can use C# as both the script language, and the output, and it can be part of the build in pretty much any build tool for C# (VisualStudio, MonoDevelop, SharpDevelop, Nant) other than just calling the compiler itself.

Create a file with a .tt or .t4 extension like:

<#@ template language="C#" #>
<#@ output extension=".generated.cs" #>
namespace SomeNamespace
{
<#
foreach(string name in new string[]{"HdlA", "HdlB", /*… and so on… */})
{#>
    public struct <#=name#>
    {
        private IntPtr _h;
        public bool IsValid
        {
            get { return (_h != IntPtr.Zero); }
        }
        public <#=name#>(IntPtr h)
        {
            _h = h;
        }
        public void Invalidate()
        {
            _h = IntPtr.Zero;
        }
        public static implicit operator IntPtr(<#=name#> hdl)
        {
            return hdl._h;
        }
    }
<#}#>
}

It works pretty much as ASP.NET does, but produces a local file. Here we use it to produce a .cs file that will then be part of the compilation, rebuilding the .cs file first if necessary. Alas, while it's a generated file, it causes problems if you don't include it in your source control (must start a question on that irritation).

Here is a real example of mine that's a bit more realistic. Here rather than wanting similar classes with different names, I wanted different methods with different types, but it's the same basic principle.

By the way,

// C# disallows arg-less ctor on struct

Depending on how you look at it, you could also say that it insists that you must have a predefined arg-less zero-filling constructor, that you can't get rid of or replace (there's [usually] no such method, but we do use new blah() in the calling C#). While it's possible in .NET to do otherwise, the differences in the cases where it is called or where zero-filling happens cause enough confusion that insisting that the constructor match zero-filling does save some problems.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM