在C#中确定调用对象类型

无论这是否是一个好主意,是否可以实现一个接口,其中执行函数知道调用对象的类型?

class A { private C; public int doC(int input) { return C.DoSomething(input); } } class B { private C; public int doC(int input) { return C.DoSomething(input); } } class C { public int DoSomething(int input) { if(GetType(CallingObject) == A) { return input + 1; } else if(GetType(CallingObject) == B) { return input + 2; } else { return input + 3; } } } 

在我看来,这是一个糟糕的编码实践(因为参数不会改变,但输出会)但除此之外是否可能?

我在某种情况下,我想要一些特定类型能够调用某个函数,但我无法排除对该函数的访问。 我想过有一个“类型”参数

 DoSomething(int input, Type callingobject) 

但是不能保证调用对象会使用GetType(this),而不是GetType(B)来欺骗B而不管它们自己的类型。

这会像检查callstack一样简单(相对简单)吗?


编辑

请投票给JaredPar的答案(如果你想的话)和John Feminella的答案。 我无法将这两个答案都标记为已被接受,我接受了John Feminella的答案,因为它特别符合我的要求,而Jared的答案则提出了我之前未曾考虑过的解决方案。

首先,是的,做到这一点并打破各种坚实的设计原则是一个糟糕的主意。 你应该考虑另一种方法,如果它是开放的,就像简单地使用多态 – 这似乎可以重构为一个非常明确的单一调度的情况。

其次,是的,这是可能的。 使用System.Diagnostics.StackTrace来遍历堆栈; 然后获得适当的StackFrame一级。 然后通过在StackFrame上使用GetMethod()来确定调用者是哪个方法。 请注意,构建堆栈跟踪是一项可能很昂贵的操作,并且您的方法的调用者可能会模糊实际来自哪里。


编辑:来自OP的这个评论很清楚,这可能是一个通用或多态的方法。 @devinb ,您可能想要考虑提出一个新问题,提供有关您尝试做什么的更多详细信息,我们可以看看它是否适合于一个好的解决方案。

简短的版本是我最终会有30或40个完全相同的function,只需要一两行。 – devinb(12秒前)

作为一种替代方法,您是否考虑过根据要求该类的对象类型提供不同的类。 说如下

 public interface IC { int DoSomething(); } public static CFactory { public IC GetC(Type requestingType) { if ( requestingType == typeof(BadType1) ) { return new CForBadType1(); } else if ( requestingType == typeof(OtherType) { return new CForOtherType(); } ... } } 

这将是一种更清晰的方法,而不是每种方法都基于调用对象改变它的行为。 它会干净地将关注点分离到IC的不同实现中。 此外,他们都可以代理回到真正的C实现。

编辑检查callstack

正如其他几个人所指出的那样,您可以检查callstack以确定哪个对象立即调用该函数。 但是,这并不是一种简单的方法来确定您想要特殊情况的对象之一是否正在呼叫您。 例如,我可以执行以下操作来从SomeBadObject中调用您,但是您很难确定我是这样做的。

 public class SomeBadObject { public void CallCIndirectly(C obj) { var ret = Helper.CallDoSomething(c); } } public static class Helper { public int CallDoSomething(C obj) { return obj.DoSomething(); } } 

你当然可以走回调用堆栈。 但这更加脆弱,因为当另一个对象调用DoSomething时,SomeBadObject可能是一个完全合法的路径。

从Visual Studio 2012(.NET Framework 4.5)开始,您可以使用调用者属性(VB和C#)自动将调用者信息传递给方法。

 public void TheCaller() { SomeMethod(); } public void SomeMethod([CallerMemberName] string memberName = "") { Console.WriteLine(memberName); // ==> "TheCaller" } 

调用者属性位于System.Runtime.CompilerServices名称空间中。


这是实现INotifyPropertyChanged理想选择:

 private void OnPropertyChanged([CallerMemberName]string caller = null) { var handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(caller)); } } 

例:

 private string _name; public string Name { get { return _name; } set { if (value != _name) { _name = value; OnPropertyChanged(); // Call without the optional parameter! } } } 

最简单的答案是传递发件人对象,就像使用典型发件人,eventargs方法的任何事件一样。

您的调用代码如下所示:

 return c.DoSomething(input, this); 

您的DoSomething方法只需使用IS运算符检查类型:

 public static int DoSomething(int input, object source) { if(source is A) return input + 1; else if(source is B) return input + 2; else throw new ApplicationException(); } 

这似乎是一些OOP更多的东西。 您可以将C视为具有方法的抽象类,并使A,Binheritance自C并简单地调用该方法。 这将允许您检查基础对象的类型,这显然不是欺骗。

出于好奇,你在尝试这个结构?

由于运行时内联的可能性,因此不可靠。

(几乎)始终有一个合适的设计可以满足您的需求。 如果你退后一步来描述你真正需要做的事情,我相信你至少会得到一个好的设计,不要求你不得不求助于这样的事情。

那么你可以尝试抓住堆栈跟踪并从那里确定调用者的类型,这在我看来是一种矫枉过正并且会很慢。

A,B将实现的接口怎么样?

 interface IFoo { int Value { get; } } 

然后你的DoSomething方法看起来像这样:

  public int DoSomething(int input, IFoo foo) { return input + foo.Value; } 

您可以使用System.Diagnostics.StackTrace类来创建堆栈跟踪。 然后,您可以查找与调用者关联的StackFrame 。 StackFrame有一个Method属性,您可以使用它来获取调用者的类型。

但是,上述方法不应该用于性能关键代码。

您可以检查调用堆栈,但这既昂贵又脆弱。 当您的代码被jit时,编译器可能会内联您的方法,因此当它可以在调试模式下工作时,您可以在发布模式下编译时获得不同的堆栈。

结构它就像一个事件处理程序,我相信FX警察甚至会建议你这样做

 static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) { throw new NotImplementedException(); } 

我可能有点麻烦以为我会以不同的方式处理这个问题,但……

我假设这个:

A类调用E类获取信息
C类调用E类来获取信息
两者都是通过E中的相同方法

您知道并创建了所有三个类,并且可以向公众隐藏E类的内容。 如果不是这种情况,您的控制权将永远丢失。

逻辑上:如果你可以隐藏E类的内容,你最有可能通过DLL分发。

如果你这样做,为什么不隐藏类A和C的内容(相同的方法),但允许A和C作为派生类B和D的基础。

在类A和C中,您将有一个方法来调用E类中的方法并传递结果。 在该方法中(可以由派生类使用,但内容对用户是隐藏的),您可以将长字符串作为“键”传递给类E中的方法。这些字符串不能被任何用户猜到,只能被知道到基类A,C和E.

让基类类封装了如何正确调用E方法的知识,我认为是完美的OOP实现

然后……我可以忽略这里的复杂性。