可以在接口中将方法声明标记为“ new ”,但它是否具有任何“技术”意义,或者它只是一种明确声明声明不能覆盖前一个声明的方法?
例如:
interface II1
{
new void F();
}
interface II2 : II1
{
new void F();
}
有效(C#4.0编译器不会抱怨)但似乎与以下内容不同:
interface II1
{
void F();
}
interface II2 : II1
{
void F();
}
提前感谢任何信息。
编辑:你知道隐藏在界面中的场景会有用吗?
编辑:根据此链接:Is method hiding ever a good idea(感谢Scott), 最常见的情况似乎是协变返回类型的模拟。
答案 0 :(得分:8)
第二个示例发出以下编译器警告:
'II2.F()'隐藏了继承的成员 'II1.F()'。使用新关键字if 隐藏的意图。
我想说使用new
关键字的区别正是:显示意图。
答案 1 :(得分:6)
这两者非常不同。通过使用'new',您将创建一个新的继承链。这意味着II2
的任何实现都需要实现F()
的两个版本,并且您最终调用的实际版本将取决于引用的类型。
考虑以下三种实现:
class A1 : II1
{
public void F()
{
// realizes II1.F()
}
}
class A2 : II2
{
void II1.F()
{
// realizes II1.F()
}
void II2.F()
{
// realizes II2.F()
}
}
class A3 : II2
{
public void F()
{
// realizes II1.F()
}
void II2.F()
{
// realizes II2.F()
}
}
如果您引用了A2
,则在未先转换为F()
或II1
的情况下,您将无法调用II2
的任何一个版本。
A2 a2 = new A2();
a2.F(); // invalid as both are explicitly implemented
((II1) a2).F(); // calls the II1 implementation
((II2) a2).F(); // calls the II2 implementation
如果您引用A3
,则可以直接调用II1
版本,因为它是隐式实现:
A3 a3 = new A3();
a3.F(); // calls the II1 implementation
((II2) a3).F(); // calls the II2 implementation
答案 2 :(得分:4)
我知道一个很好的用途:你有这样做来声明一个从另一个COM接口派生的COM接口。在magazine article中已经触及了它。
Fwiw,作者完全错误地认定了问题的根源,它与“遗产税”无关。 COM简单地利用了典型的C ++编译器实现多重继承的方式。每个基类都有一个v表,正是COM需要的。 CLR不会这样做,它不支持MI,并且只有一个v-table。接口方法合并到基类的v表中。
答案 3 :(得分:1)
new修饰符非常简单,它只是抑制隐藏方法时创建的编译器警告。如果在不隐藏其他方法的方法上使用,则会生成警告。
来自The C# Language Specification 3.0
10.3.4新修饰语 允许类成员声明声明与继承成员具有相同名称或签名的成员。发生这种情况时,称派生类成员隐藏基类成员。隐藏继承的成员不会被视为错误,但它确实会导致编译器发出警告。为了抑制警告,派生类成员的声明可以包含一个new修饰符,以指示派生成员是否要隐藏基本成员。本主题将在§3.7.1.2中进一步讨论。 如果新修饰符包含在不隐藏继承成员的声明中,则会发出对该效果的警告。删除新修饰符可以抑制此警告。
答案 4 :(得分:1)
Fredrik回答后的一个例子。我希望有一个接口,表示通过id获取实体的方法。然后我想要一个WCF服务和一些其他标准存储库能够用作该接口。为了使用WCF,我需要用属性来装饰我的界面。从一个意图的角度来看,我不想用WCF的东西装饰我的界面,因为它不会仅通过WCF使用,所以我需要使用new。这是代码:
public class Dog
{
public int Id { get; set; }
}
public interface IGetById<TEntity>
{
TEntity GetById(int id);
}
public interface IDogRepository : IGetById<Dog> { }
public class DogRepository : IDogRepository
{
public Dog GetById(int id)
{
throw new NotImplementedException();
}
}
[ServiceContract]
public interface IWcfService : IGetById<Dog>
{
[OperationContract(Name="GetDogById")]
new Dog GetById(int id);
}
答案 5 :(得分:1)
我几乎在每个界面都使用它。看这里:
interface ICreature
{
/// <summary>
/// Creature's age
/// </summary>
/// <returns>
/// From 0 to int.Max
/// </returns>
int GetAge();
}
interface IHuman : ICreature
{
/// <summary>
/// Human's age
/// </summary>
/// <returns>
/// From 0 to 999
/// </returns>
new int GetAge();
}
拥有较少要求但义务较大的继承成员是绝对正常的。 LSP没有被违反。但如果是的话,在哪里记录新合同?
答案 6 :(得分:1)
通读了这些答案后,他们很好地解释了新操作符 的作用,但是我对OP的这一部分问题没有明确的答案:
您知道隐藏在界面中会很有用的情况吗?
总而言之,所有这些都归结为可测试性和重用性。通过将接口分成较小的块,并坚持使用Interface Separation Principle,我们可以使类的用户最少地依赖无关的细节,并最大程度地分离,这为我们提供了更多的可重用性选项,并且使测试时间更短
通常,当我们需要以不可避免的方法冲突的方式分支接口类型层次结构时,新运算符会在此处起作用。所有这些听起来有点抽象,很难解释,所以我创建了一个我认为是一个最小的示例,我们希望将接口类型层次结构分为两个,同时有一个通用的共享方法。我将代码放在.NET小提琴上:
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
。这样,它们可以被最大程度地重用,例如除了长方体以外的其他形状,也可以更轻松地进行测试。在实践中,最后一点很关键。使用更少的方法来模拟接口要容易得多,尤其是如果主接口类型中的方法包含对我们无法控制的类型的引用。当然,总是可以通过一个模拟框架解决这个问题,但是我更喜欢保持代码的核心干净。
那么new
关键字在哪里适合呢?好吧,由于VolumeUserSmart
和HeightUserSmart
都需要访问Name
属性,因此我们必须在IThingWithHeight
和IThingWithVolume
中都声明它。因此,我们必须在子接口ICuboidSmart
中重新声明它,否则我们将得到抱怨歧义性的编译器错误。在这种情况下,我们要做的是隐藏Name
和IThingWithHeight
中定义的IThingWithVolume
的两个版本,否则这两个版本会发生冲突。而且,就像其他答案所指出的那样,尽管我们在这里没有使用new
,但是我们应该明确表明我们的隐藏意图。