ImmutableArray如何正确使用
ImmutableArray how to use properly
我正在尝试创建一个永远无法更改的常量值数组。我尝试这样的事情....
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", IsFrozen = false, 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 属性。
- 我建议使用
IReadOnlyList<T>
而不是 ImmutableArray<T>
,因为它内置于 .NET 的 BCL 而 ImmutableArray<T>
添加了对 System.Collections.Immutable
.[=47= 的依赖]
- 请注意,属性 被内联初始化为
Array
(ColItem[]
),但因为 仅 对它的引用是通过 IReadOnlyList<T>
那么不使用反射就无法更改集合。
- 使其成为
{ get; }
-only 属性 意味着您的 class 的消费者无法重新分配 属性 值。
- 如果您确实需要使用字段(而不是 属性),则将其设为
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", IsFrozen:false, 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
class 变成 record ColItem
:
public record ColItem(
string Header,
string HeaderKey,
bool IsFrozen = false,
bool IsVisible = true,
int DisplayIdx
);
让你的 ColItem
class 不可变。有几种方法,但最直接的方法是从您的属性中删除 set
关键字,使它们成为 readonly
如果你想制作一个不可变的引用类型,你可以将所有属性设为私有设置器。或者在 C# 9 中,您可以仅使用 init 属性,这些属性可以使用对象初始值设定项
构造
public string Header { get; init; }
或者您可以更进一步,创建一个 Record
,它具有其他几个优点,例如 With-expressions、Value-基于相等性 并且不同于具有继承
的结构
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 property or indexer 'ColItem.HeaderKey' can only be
assigned in an object initializer, or on 'this' or 'base' in an
instance constructor or an 'init' accessor.
那么剩下的就是选择一个不可变的集合/集合接口
以下是使 ColItem
class 不可变的方法:
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", false),
new ColItem ("PRICETBL_BIDQTY"),
new ColItem ("PRICETBL_BID"),
new ColItem ("PRICETBL_ASK"),
new ColItem ("PRICETBL_ASKQTY"),
});
}
我正在尝试创建一个永远无法更改的常量值数组。我尝试这样的事情....
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", IsFrozen = false, 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 属性。 - 我建议使用
IReadOnlyList<T>
而不是ImmutableArray<T>
,因为它内置于 .NET 的 BCL 而ImmutableArray<T>
添加了对System.Collections.Immutable
.[=47= 的依赖] - 请注意,属性 被内联初始化为
Array
(ColItem[]
),但因为 仅 对它的引用是通过IReadOnlyList<T>
那么不使用反射就无法更改集合。- 使其成为
{ get; }
-only 属性 意味着您的 class 的消费者无法重新分配 属性 值。 - 如果您确实需要使用字段(而不是 属性),则将其设为
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", IsFrozen:false, 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
class 变成 record ColItem
:
public record ColItem(
string Header,
string HeaderKey,
bool IsFrozen = false,
bool IsVisible = true,
int DisplayIdx
);
让你的 ColItem
class 不可变。有几种方法,但最直接的方法是从您的属性中删除 set
关键字,使它们成为 readonly
如果你想制作一个不可变的引用类型,你可以将所有属性设为私有设置器。或者在 C# 9 中,您可以仅使用 init 属性,这些属性可以使用对象初始值设定项
构造public string Header { get; init; }
或者您可以更进一步,创建一个 Record
,它具有其他几个优点,例如 With-expressions、Value-基于相等性 并且不同于具有继承
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 property or indexer 'ColItem.HeaderKey' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor.
那么剩下的就是选择一个不可变的集合/集合接口
以下是使 ColItem
class 不可变的方法:
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", false),
new ColItem ("PRICETBL_BIDQTY"),
new ColItem ("PRICETBL_BID"),
new ColItem ("PRICETBL_ASK"),
new ColItem ("PRICETBL_ASKQTY"),
});
}