通读这些答案后,他们很好地解释了 new 运算符的作用,但我看不到对 OP 问题的这一部分的任何明确答案:
你知道隐藏在界面中会很有用的场景吗?
总而言之,这一切都归结为可测试性和重用性。通过将接口拆分成更小的块,并遵守Interface Separation Principle,我们可以使我们的类的用户最小地依赖无关的细节并最大程度地解耦,这为我们提供了更多的可重用性选项和更轻松的测试时间。
一般来说,当我们需要以不可避免的方法冲突的方式分支接口类型层次结构时,new 运算符会在这里发挥作用。这一切听起来有点抽象,很难解释,所以我创建了一个我认为是最小的示例,我们希望将接口类型层次结构一分为二,同时有一个通用的共享方法。我把代码放在 .NET fiddle 上:
https://dotnetfiddle.net/kRQpoU
又来了:
using System;
public class Program
{
public static void Main()
{
//Simple usage
var simpleCuboid = new MockCuboidSimple();
var heightUser = new HeightUserEntangled();
var volumeUser = new VolumeUserEntangled();
Console.WriteLine("*** Simple Case ***");
Console.WriteLine(heightUser.PrintHeight(simpleCuboid));
Console.WriteLine(volumeUser.PrintVolume(simpleCuboid));
//Smart usage - the same behaviour, but more testable behind the scenes!
var smartCuboid = new MockCuboidSmart();
var heightUserSmart = new HeightUserSmart();
var volumeUserSmart = new VolumeUserSmart();
Console.WriteLine("*** smart Case ***");
Console.WriteLine(heightUserSmart.PrintHeight(smartCuboid));
Console.WriteLine(volumeUserSmart.PrintVolume(smartCuboid));
}
}
//Disentangled
class VolumeUserSmart
{
public string PrintVolume(IThingWithVolume volumeThing)
{
return string.Format("Object {0} has volume {1}", volumeThing.Name, volumeThing.Volume);
}
}
class HeightUserSmart
{
public string PrintHeight(IThingWithHeight heightThing)
{
return string.Format("Object {0} has height {1}", heightThing.Name, heightThing.Height);
}
}
class MockCuboidSmart : ICuboidSmart
{
public string Name => "Mrs. Cuboid";
public double Height => 3.333;
public double Width => 31.23432;
public double Length => 123.12;
public double Volume => Height * Width * Length;
}
interface ICuboidSmart : IThingWithHeight, IThingWithVolume
{
//Here's where we use new, to be explicit about our intentions
new string Name {get;}
double Width {get;}
double Length {get;}
//Potentially more methods here using external types over which we have no control - hard to mock up for testing
}
interface IThingWithHeight
{
string Name {get;}
double Height {get;}
}
interface IThingWithVolume
{
string Name {get;}
double Volume {get;}
}
//Entangled
class VolumeUserEntangled
{
public string PrintVolume(ICuboidSimple cuboid)
{
return string.Format("Object {0} has volume {1}", cuboid.Name, cuboid.Volume);
}
}
class HeightUserEntangled
{
public string PrintHeight(ICuboidSimple cuboid)
{
return string.Format("Object {0} has height {1}", cuboid.Name, cuboid.Height);
}
}
class MockCuboidSimple : ICuboidSimple
{
public string Name => "Mrs. Cuboid";
public double Height => 3.333;
public double Width => 31.23432;
public double Length => 123.12;
public double Volume => Height * Width * Length;
}
interface ICuboidSimple
{
string Name {get;}
double Height {get;}
double Width {get;}
double Length {get;}
double Volume {get;}
//Potentially more methods here using external types over which we have no control - hard to mock up for testing
}
请注意,VolumeUserSmart 和 HeightUserSmart 仅依赖于它们关心的 ICuboidSmart 接口的片段,即 IThingWithHeight 和 IThingWithVolume。这样,它们可以最大程度地重复使用,例如对于长方体以外的形状,也可以更容易地测试。最后一点是,我在实践中发现,至关重要。用更少的方法来模拟一个接口要容易得多,尤其是当主接口类型中的方法包含对我们无法控制的类型的引用时。当然,总是可以通过一个 mocking 框架来解决这个问题,但我更喜欢保持代码干净的核心。
那么new 关键字在哪里适合?好吧,既然VolumeUserSmart 和HeightUserSmart 都需要访问Name 属性,我们必须在IThingWithHeight 和IThingWithVolume 中声明它。因此我们必须在子接口ICuboidSmart 中重新声明它,否则我们会得到一个编译器错误,抱怨歧义。在这种情况下,我们正在做的是隐藏在IThingWithHeight 和IThingWithVolume 中定义的Name 的两个版本,否则它们会发生冲突。而且,就像其他答案指出的那样,虽然我们没有必须在这里使用new,但我们应该明确说明我们隐藏的意图。