微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

ImmutableArray 如何正确使用

如何解决ImmutableArray 如何正确使用

我正在尝试创建一个永远不会改变的常量值数组。我尝试这样的事情....

public class ColItem
{
    public string Header { get; set; }              // real header name in ENG/CHI
    public string HeaderKey { get; set; }           // header identifier
    public bool IsFrozen { get; set; } = false;
    public bool IsVisible { get; set; } = true;
    public int displayIdx { get; set; }
}

public struct DatagridSettingDefault
{
    public static ImmutableArray<ColItem> DataGrid_MarketPriceTab = ImmutableArray.Create(new ColItem[] {
        new ColItem { HeaderKey = "ORDER_ID",IsFrozen = true,displayIdx = 0 },new ColItem { HeaderKey = "PRICE_RENAMEPROMPT",IsFrozen = false,displayIdx = 1 },new ColItem { HeaderKey = "PRICETBL_TradESTATENO",displayIdx = 2 },new ColItem { HeaderKey = "PRICETBL_BIDQTY",displayIdx = 3 },new ColItem { HeaderKey = "PRICETBL_BID",displayIdx = 4 },new ColItem { HeaderKey = "PRICETBL_ASK",displayIdx = 5 },new ColItem { HeaderKey = "PRICETBL_ASKQTY",displayIdx = 6 }
}

然而,这个数组仍然会从代码的某个地方(另一个线程)改变。 如何使数组保持不变并且在程序的整个生命周期内绝对不能改变? 我只需要在编译时初始化它,无论什么引用或其他线程改变它,它都不应该改变。但我的情况,它一直在变化。

例如,如果我稍后在代码中或通过另一个线程执行这样的操作,它就会发生变化......

HeaderKey = col.Header.ToString(),Header = "whatever"
IsFrozen = col.IsFrozen,IsVisible = true,displayIdx = col.displayIndex

我该如何解决

解决方法

您不需要ImmutableArray<T>。只需这样做:

  • DataGrid_MarketPriceTab 从可变字段更改为 getter-only 属性。
  • 我建议使用 IReadOnlyList<T> 而不是 ImmutableArray<T>,因为它内置在 .NET 的 BCL 中,而 ImmutableArray<T> 添加了对 System.Collections.Immutable 的依赖。
  • 请注意,该属性被内联初始化为 Array (ColItem[]),但因为 only 对它的引用是通过 IReadOnlyList<T>,所以集合不能在不使用反射的情况下改变。
    • 使其成为仅限 { get; } 的属性意味着您的类的使用者无法重新分配属性值。
    • 如果您确实需要使用字段(而不是属性),请将其设为 static readonly 字段。

您还需要使 ColItem 不可变 - 这可以通过使所有成员属性 { get; } 而不是 { get; set; }(并确保他们的类型也是不可变的):

像这样:

// This is an immutable type: properties can only be set in the constructor.
public class ColItem
{
    public ColItem(
        string headerName,string headerKey,int displayIdx,bool isFrozen = false,bool isVisible = true
    )
    {
        this.Header     = headerName ?? throw new ArgumentNullException(nameof(headerName));
        this.HeaderKey  = headerKey ?? throw new ArgumentNullException(nameof(headerKey));
        this.IsFrozen   = isFrozen;
        this.IsVisible  = isVisible;
        this.DisplayIdx = displayIdx;
    }

    public string Header     { get; }
    public string HeaderKey  { get; }
    public bool   IsFrozen   { get; }
    public bool   IsVisible  { get; }
    public int    DisplayIdx { get; }
}

public struct DatagridSettingDefault // Why is this a struct and not a static class?
{
    public static IReadOnlyList<ColItem> DataGrid_MarketPriceTab { get; } = new[]
    {
        new ColItem( headerName: "Order Id",headerKey: "ORDER_ID",isFrozen: true,displayIdx: 0 ),new ColItem( headerName: "Price",headerKey: "PRICE_RENAMEPROMPT",IsFrozen:false,displayIdx:1 ),new ColItem( headerName: "Foo",headerKey: "PRICETBL_TRADESTATENO",displayIdx:2 ),new ColItem( headerName: "Bar",headerKey: "PRICETBL_BIDQTY",displayIdx:3 ),new ColItem( headerName: "Baz",headerKey: "PRICETBL_BID",displayIdx:4 ),new ColItem( headerName: "Qux",headerKey: "PRICETBL_ASK",displayIdx:5 ),new ColItem( headerName: "Hmmm",headerKey: "PRICETBL_ASKQTY",displayIdx:6 )
    };
}

如果你觉得这一切都很乏味,我同意!好消息是 if you're using C# 9.0 or later (which requires .NET 5,unfortunately) you can use Immutable Record Types,所以 class ColItem 类变成了 record ColItem:

public record ColItem(
    string Header,string HeaderKey,bool   IsFrozen = false,bool   IsVisible = true,int    DisplayIdx
);
,

使您的 ColItem 类不可变。有几种方法可以做到这一点,但最直接的方法是从您的属性中删除 set 关键字,使它们 readonly

,

如果你想创建一个不可变的引用类型,你可以将所有属性设为私有 setter。或者在 C# 9 中你可以只使用 init 属性,这些属性可以用对象初始化器构造

public string Header { get; init; } 

或者您可以更进一步,创建一个 Record,它具有其他几个优点,例如 With-expressions基于值的相等性 和与具有继承

的结构不同
public record ColItem
{
   public string Header { get; init; } // real header name in ENG/CHI
   public string HeaderKey { get; init; } // header identifier
   public bool IsFrozen { get; init; } = false;
   public bool IsVisible { get; init; } = true;
   public int DisplayIdx { get; init; }
}

使用

 var test = new DatagridSettingDefault();
 test.DataGrid_MarketPriceTab[0].HeaderKey = "asd";

会产生以下编译错误

CS8852 Init-only 属性或索引器 'ColItem.HeaderKey' 只能是 在对象初始值设定项中分配,或在“this”或“base”中分配 实例构造函数或“init”访问器。

那么剩下的就是选择一个不可变的集合/集合接口

,

以下是使 ColItem 类不可变的方法:

public class ColItem
{
    public string HeaderKey { get; }
    public bool IsFrozen { get; }

    public ColItem(string headerKey,bool isFrozen = false)
    {
        HeaderKey = headerKey;
        IsFrozen = isFrozen;
    }
}

通过仅声明 get 访问器,属性在除类型构造函数之外的任何地方都变得不可变。 (docs)

在 C# 9 中,还有 { get; init; } 属性的选项,这些属性在初始化期间可变,之后变为不可变,以及默认情况下不可变的新 record 类型。>

这里是如何使静态属性 DatagridSettingDefault.DataGrid_MarketPriceTab 不可变,方法是将其标记为 readonly

public class DatagridSettingDefault
{
    public static readonly ImmutableArray<ColItem> DataGrid_MarketPriceTab
        = ImmutableArray.Create(new ColItem[]
    {
        new ColItem ("ORDER_ID",true),new ColItem ("PRICE_RENAMEPROMPT",false),new ColItem ("PRICETBL_TRADESTATENO",new ColItem ("PRICETBL_BIDQTY"),new ColItem ("PRICETBL_BID"),new ColItem ("PRICETBL_ASK"),new ColItem ("PRICETBL_ASKQTY"),});
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。