C# 在.NET中是否可以使用不可变数组?
是否可能以某种方式将C# 在.NET中是否可以使用不可变数组?,c#,.net,arrays,immutability,C#,.net,Arrays,Immutability,是否可能以某种方式将System.Array标记为不可变。当放置在公共get/private集合之后时,它们不能被添加到其中,因为它需要重新分配和重新分配,但是消费者仍然可以设置他们想要的任何下标: public class Immy { public string[] { get; private set; } } 我认为readonly关键字可能会起作用,但没有这样的运气。我认为最佳做法是在公共API中使用IList,而不是数组readonly将阻止在构造函数之外设置成员变量,但正
System.Array
标记为不可变。当放置在公共get/private集合之后时,它们不能被添加到其中,因为它需要重新分配和重新分配,但是消费者仍然可以设置他们想要的任何下标:
public class Immy
{
public string[] { get; private set; }
}
我认为
readonly
关键字可能会起作用,但没有这样的运气。我认为最佳做法是在公共API中使用IList,而不是数组readonly将阻止在构造函数之外设置成员变量,但正如您所发现的,不会阻止人们在数组中分配元素
// bad code
// could still do Path.InvalidPathChars[0] = 'A';
public sealed class Path {
public static readonly char[] InvalidPathChars =
{ '\"', '<', '>', '|' };
}
有关更多信息,请参阅
编辑:数组不能是只读的,但是可以通过Array.AsReadOnly()将它们转换为只读的IList实现,正如@shahkalpesh所指出的。可能就是您想要的。它没有添加()方法。可以使用Array.AsReadOnly方法返回。建议返回数组的副本。这样,消费者就无法更改阵列中的项目
// bad code
// could still do Path.InvalidPathChars[0] = 'A';
public sealed class Path {
public static readonly char[] InvalidPathChars =
{ '\"', '<', '>', '|' };
}
//错误代码
//仍然无法执行Path.InvalidPathChars[0]=“A”;
公共密封类路径{
公共静态只读字符[]InvalidPathChars=
{ '\"', '', '|' };
}
这些更好:
public static ReadOnlyCollection<char> GetInvalidPathChars(){
return Array.AsReadOnly(InvalidPathChars);
}
public static char[] GetInvalidPathChars(){
return (char[])InvalidPathChars.Clone();
}
公共静态只读集合GetInvalidPathChars(){
返回数组.AsReadOnly(InvalidPathChars);
}
公共静态字符[]GetInvalidPathChars(){
返回(char[])InvalidPathChars.Clone();
}
这些例子都是直接从书中获得的。唯一需要添加的是数组意味着可变性。当您从函数返回数组时,您是在向客户端程序员建议,他们可以/应该更改内容。继Matt的回答之后,IList是数组的一个完整抽象接口,因此它允许添加、删除等操作。我不是确定为什么Lippert似乎建议在需要不变性的情况下将其作为IEnumerable的替代品。(编辑:,因为如果您喜欢,IList实现可以为这些变异方法抛出异常) 也许还有一件事需要记住,列表中的项目也可能具有可变状态。如果您确实不希望调用方修改这种状态,您可以选择: 确保列表中的项目是不可变的(如您的示例:string是不可变的) 返回所有内容的深度克隆,因此在这种情况下,无论如何都可以使用数组 返回一个接口,该接口允许对项目进行只读访问:
interface IImmutable
{
public string ValuableCustomerData { get; }
}
class Mutable, IImmutable
{
public string ValuableCustomerData { get; set; }
}
public class Immy
{
private List<Mutable> _mutableList = new List<Mutable>();
public IEnumerable<IImmutable> ImmutableItems
{
get { return _mutableList.Cast<IMutable>(); }
}
}
接口IImmutable
{
公共字符串ValuableCustomerData{get;}
}
类可变,类可变
{
公共字符串ValuableCustomerData{get;set;}
}
公共类移民
{
私有列表_mutableList=新列表();
公共IEnumerable不可变项
{
获取{return _mutableList.Cast();}
}
}
请注意,从IImmutable接口访问的每个值本身都必须是不可变的(例如字符串),或者是您动态创建的副本。您希望做的最好的事情是扩展现有集合以构建您自己的集合。最大的问题是,它的工作方式必须不同于所有现有集合类型,因为每次调用都必须返回一个新集合。请参阅基类库中的(目前正在预览中)。.NET倾向于在所有情况下避开阵列,但最简单和最传统的用例除外。对于其他所有情况,都有各种可枚举/集合实现 如果要将一组数据标记为不可变,则超出了传统数组提供的功能。.NET提供了等效的功能,但从技术上讲不是以数组的形式。要从数组中获取不可变的集合,请使用:
immutable
将是一个实例。作为更通用的用例,您可以从任何通用实现创建一个
var immutable = new ReadOnlyCollection<char>(new List<char>(mutable));
更准确地说,它没有公开的Add()方法。它确实有一个,因为接口需要它。但是好的接口代码会检查。在调用它之前先只读,因为这样做会导致异常。这与不可变数组不同--
ReadOnlyCollection
不允许任何修改,但它只包装一个常规的列表,它创建者仍然可以更改。@HenryJackson最终,.NET中的所有数据都可以通过反射进行更改。因此,有意义的不可变性只不过是使不可访问的不可变性变得不可访问。如果对可变集合的引用被丢弃,则对可变集合的只读包装器当然会被视为不可变的,从而造成变异ible.@AndrewArnott:不幸的是,无论是ReadOnlyCollection
还是任何其他类似的框架类型,都没有提供任何方法来保证实例的备份存储是不可变的(例如,因为包装器创建了原始数组的克隆,它在宇宙中的任何地方都是对原始数组的唯一引用)。在许多情况下,接收集合的代码应该创建快照,除非它是不可变的,在这种情况下,不应该麻烦,但无法确定是否需要快照。如果我没有错的话,不可变和只读是不一样的。这是一种约定还是客户端需要快照的更具体原因这种行为?该属性缺少名称,不是吗?该属性称为“不可变”-从这个意义上说,字符串是不可变的(除非您在不安全的代码中访问字符指针,因为它们是被占用的,所以您确实不应该这样做。)请记住,不可变数组只与其成员一样不可变。@PhilWhittington他在代码中的意思是,属性缺少名称。这在语法上是不正确的。但它并不是真正的工作示例。我知道这是几年前的事,但Svish指出,您应该有公共字符串[]variablename{…}他说,那财产不见了
// Note that .NET favors Count over Length; all but traditional arrays use Count:
for (var i = 0; i < immutable.Count; i++)
{
// this[] { get } is present, as ReadOnlyCollection<T> implements IList<T>:
var element = immutable[i]; // Works
// this[] { set } has to be present, as it is required by IList<T>, but it
// will throw a NotSupportedException:
immutable[i] = element; // Exception!
}
// ReadOnlyCollection<T> implements IEnumerable<T>, of course:
foreach (var character in immutable)
{
}
// LINQ works fine; idem
var lowercase =
from c in immutable
where c >= 'a' && c <= 'z'
select c;
// You can always evaluate IEnumerable<T> implementations to arrays with LINQ:
var mutableCopy = immutable.ToArray();
// mutableCopy is: new[] { 'a', 'A', 'b', 'B', 'c', 'C' }
var lowercaseArray = lowercase.ToArray();
// lowercaseArray is: new[] { 'a', 'b', 'c' }