这个例子是用C#语言编写的,但是这个问题确实适用于任何OO语言。我想创建一个实现IReadOnlyList的通用的,不可变的类。此外,此类应具有无法修改的基础通用IList。最初,该类的编写方式如下:
public class Datum<T> : IReadOnlyList<T>
{
private IList<T> objects;
public int Count
{
get;
private set;
}
public T this[int i]
{
get
{
return objects[i];
}
private set
{
this.objects[i] = value;
}
}
public Datum(IList<T> obj)
{
this.objects = obj;
this.Count = obj.Count;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
public IEnumerator<T> GetEnumerator()
{
return this.objects.GetEnumerator();
}
}
但是,这不是一成不变的。如您所知,更改初始IList'obj'会更改Datum的'objects'。
static void Main(string[] args)
{
List<object> list = new List<object>();
list.Add("one");
Datum<object> datum = new Datum<object>(list);
list[0] = "two";
Console.WriteLine(datum[0]);
}
这会将“两个”写入控制台。由于Datum的重点是不可变性,所以这是不可行的。为了解决这个问题,我重写了Datum的构造函数:
public Datum(IList<T> obj)
{
this.objects = new List<T>();
foreach(T t in obj)
{
this.objects.Add(t);
}
this.Count = obj.Count;
}
经过与之前相同的测试,控制台上将显示“一个”。伟大的。但是,如果Datum包含一个不可变集合的集合,并且其中一个不可变集合被修改了怎么办?
static void Main(string[] args)
{
List<object> list = new List<object>();
List<List<object>> containingList = new List<List<object>>();
list.Add("one");
containingList.Add(list);
Datum<List<object>> d = new Datum<List<object>>(containingList);
list[0] = "two";
Console.WriteLine(d[0][0]);
}
并且,正如预期的那样,控制台上会打印出“两个”。因此,我的问题是,如何使此类真正不可变?
最佳答案
不能。 或者,您不想这么做,因为这样做的方式太糟糕了。这里有一些:
1.仅struct
将where T : struct
添加到您的Datum<T>
类。 struct
是usually immutable,但是如果它包含可变的class
实例,则仍然可以对其进行修改(感谢Servy)。主要缺点是所有类都淘汰了,甚至像string
这样的不可变类以及您创建的任何不可变类。
var e = new ExtraEvilStruct();
e.Mutable = new Mutable { MyVal = 1 };
Datum<ExtraEvilStruct> datum = new Datum<ExtraEvilStruct>(new[] { e });
e.Mutable.MyVal = 2;
Console.WriteLine(datum[0].Mutable.MyVal); // 2
2.创建一个界面
创建一个marker interface并将其实现在您创建的任何不可变类型上。主要缺点是所有内置类型均已淘汰。而且您并不真正知道实现此目标的类是否确实是不可变的。
public interface IImmutable
{
// this space intentionally left blank, except for this comment
}
public class Datum<T> : IReadOnlyList<T> where T : IImmutable
3.序列化!
如果您对传递的对象进行序列化和反序列化(例如,使用Json.NET),则可以创建它们的完全独立的副本。优势:可与许多内置和自定义类型一起使用,您可能希望将其放置在此处。缺点:需要额外的时间和内存来创建只读列表,并且要求您的对象可序列化而不丢失任何重要内容。预期到列表外对象的任何链接都将被销毁。
public Datum(IList<T> obj)
{
this.objects =
JsonConvert.DeserializeObject<IList<T>>(JsonConvert.SerializeObject(obj));
this.Count = obj.Count;
}
我建议您仅记录
Datum<T>
即可说该类仅应用于存储不可变类型。这种类型的未强制执行的隐式要求在其他类型中也存在(例如Dictionary
期望TKey
以预期的方式(包括不可变性)实现GetHashCode
和Equals
,因为很难做到这一点。关于c# - 如何确保泛型的不变性,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/27322596/