C# 类实例属性机制

本文关键字:机制 属性 实例 | 更新日期: 2023-09-27 18:36:10

C# 中是否有一种理智的方法来实现以下构造(在伪代码中):

void Method(MyClass<Attribute1, Attribute2, ...> foo)
{
  // here I am guaranteed that foo has the specified attributes
}

例如,Attribute*在哪里是枚举值,以便只有使用方法所需的属性实例化的MyClass实例才能传递给该方法(否则无法编译)?

我尝试查看泛型,因为我知道C++模板都可以做到这一点,所以这似乎是一个合乎逻辑的起点,但我无法让它优雅地工作(我尝试使用接口以这种方式约束参数的类型,但它非常笨重,坦率地说无法使用,因为我至少有 4 个属性)。

我想这样做是为了避免在每个方法的开头进行大量烦人的检查。我正在做 DirectX 11 图形开发,所以我有点受到 API 的限制,它不会特别容易以这种"类型安全"的方式传递对象(在 DirectX 中,每个资源都有一个大的"描述"结构,其中包含有关资源可以做什么和不能做什么、是什么和不做什么等信息,并且很乏味且容易出错解析, 所以我正在尝试为我和我的用户编写一个包装器)。

我也不能为每种情况提供不同的类类型,因为有很多组合,所以这似乎是编写此类代码的最舒适的方法,我希望 C# 使这成为可能。


我确定这种语言功能有一个名称(如果您知道它,请告诉我,我会用谷歌搜索,但是当您不知道正确的关键字时,这很难搜索......

C# 类实例属性机制

.NET 中的泛型类型参数必须是类型本身。不能创建仅特定于泛型类型参数的特定值的泛型类型/方法。

如果不希望或无法创建表示希望将方法限制为的属性值的类型,则必须在方法中进行健全性检查,以确保在提供的"foo"对象中使用正确的属性值。


使用特定类型作为特定属性值的表示形式可能是您提出的问题的答案,但它的缺点是不支持开关大小写语句(详见下文)。也请阅读我回答末尾的最后注释。

比如说,你想要一个表示纹理的类型。纹理可以具有不同的通道数和不同的位深度。然后,您可以声明一个通用纹理类型,如下所示:

class Texture<TChannels, TBPC>
    where TChannels : INumOfChannels,new()
    where TBPC : IBitsPerChannel,new()

INumOfChannelsIBitsPerChannel只是接口,可以是空的。
new() 约束阻止使用接口本身创建具体的纹理类型。

对于不同的通道和不同的 BPC,您将创建从各自的基本接口扩展的空类型,例如:

class FourChannels : INumOfChannels {};
class ThreeChannels : INumOfChannels {};
class BitsPerChannel_24 : IBitsPerChannel {};
class BitsPerChannel_32 : IBitsPerChannel {};

使用此功能,可以将泛型方法限制为某些属性组合。如果您的方法只处理 4 通道和 32bpc 纹理:

void MyMethod<TChannels, TBPC>(Texture<TChannels, TBPC> foo)
    where TChannels : FourChannels
    where TBPC : BitsPerChannel_32


现在,每一件好事也有黑暗的一面。你会怎么做这样的事情(写成伪代码)?

switch (NumOfChannelsAttribute)
{
    case FourChannels:
        // do something
        break;
    case ThreeChannels:
        // do something else
        break;
}

你不能,至少不能以简单和简单的方式,因为"四通道"和"三通道"是类型,而不是整数值。

当然,您仍然可以使用 if 构造。为此,您需要在通用纹理类型中实现一个属性,该属性提供使用的属性:

class Texture<TChannels, TBPC> where TChannels : INumOfChannels,new() where TBPC : IBitsPerChannel,new()
{
    public Type ChannelsAttribute
    {
        get { return typeof(TChannels); }
    }
    public Type BitsPerChannelAttribute
    {
        get { return typeof(TBPC); }
    }
}

if 构造中,您可以按如下方式使用它:

var myTex = new Texture<FourChannels, BitsPerChannel_32>();
if (myTex.ChannelsAttribute == typeof(FourChannels))
{
    ... do something with 4 channels
}
else
{
    ... though luck, only 4 channels are supported...
}


最后的注意事项和建议:
虽然这可能适用于您的问题,但诉诸这些"技巧"通常表明设计存在缺陷。我认为,如果您重新审视在代码中所做的设计选择,那么这是值得投入的时间,因此您无需依赖这样的拐杖。

C# 没有这样的功能。您提到您已经尝试使用接口,但没有指定如何使用。我建议您尝试使用它们的方法是使用具有多个约束的泛型,例如

void Method(T foo) where T : IAttribute1, IAttribute2, IAttribute3, IAttribute4
{
}

假设一个这样的属性类是 ICpuMappable,那么您可以约束可用于Method1的类型:

void Method1(T foo) where T : ICpuMappable
{
}

您可以知道传递给Method1的任何foo都是可映射的。

你最终可能会得到很多接口,但由于许多接口将被视为"标志",它们应该不太难维护。