【问题标题】:Determine Calling Object Type in C#在 C# 中确定调用对象类型
【发布时间】:2010-10-14 01:58:18
【问题描述】:

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

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,而不管它们自己的类型如何。

这会像检查调用堆栈一样简单(相对简单)吗?

【问题讨论】:

  • 您能否提供更多背景信息?因为现在我不明白为什么你不只是在 C 中实现单独的方法。像 C.DoSometingA 和 C.DoSomethingB 这样的东西。甚至更好:但是 A & B 类中的行为。
  • 简短的版本是,我最终会得到 30 或 40 个相同的函数,它们只是相差一两行。
  • @devinb:请参阅下面我对帖子所做的编辑。您可能需要考虑提出一个不同的问题。
  • 我认为你是对的,但是,我的想法如雨后春笋般涌现的(非常善良和专业的)滥用意味着我预期的重新设计将比我想象的要重要得多.因此,我还不能简洁地提出一个更好的问题。
  • @devinb:好吧,我希望我们不要太苛刻! :) 但是一旦你制定了它,请跟进——我很想听听细节。

标签: c# types


【解决方案1】:

首先,是的,这样做是一个糟糕的主意,并且违反了各种可靠的设计原则。如果它是开放的,你绝对应该考虑另一种方法,比如简单地使用多态性——这似乎可以重构为一个非常明确的单分派案例。

其次,是的,这是可能的。使用System.Diagnostics.StackTrace 遍历堆栈;然后获取适当的StackFrame 上一级。然后通过在该StackFrame 上使用GetMethod() 来确定调用者是哪个方法。请注意,构建堆栈跟踪可能是一项代价高昂的操作,并且您的方法的调用者可能会掩盖事物的真正来源。


编辑: OP 的这条评论清楚地表明这可能是一个通用或多态方法。 @devinb,您可能需要考虑提出一个新问题,提供有关您正在尝试做的事情的更多详细信息,我们可以看看它是否适合一个好的解决方案。

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

【讨论】:

  • 天啊!删除我的“重复答案”。 . .笨笨笨慢的手指。 . . +1 顺便说一句
  • 不费吹灰之力——我总是被那个讨厌的 Jon Skeet 弄得像个忍者。顺便说一句,我不会删除您的帖子,除非它非常接近完全相同的副本。如果您提出的观点与我的大部分相似但在某些方面有所不同,请务必保留它。
【解决方案2】:

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

【讨论】:

  • 我的设计是一个非常紧密耦合和自我引用的噩梦,所以是的,我想出的设计不会有这种特别的丑陋,但它们有自己的丑陋。我只是在尝试探索每个选项。
  • @devinb 我发现其他选项不太可能像这样丑陋
  • 它们更复杂。与这个相对简单但可怕的相反。 =D 我主要是好奇。
【解决方案3】:

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

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 实现。

编辑检查调用堆栈

正如其他几个人指出的那样,您可以检查调用堆栈以确定哪个对象正在立即调用该函数。但是,这不是确定您想要特殊情况的对象之一是否正在呼叫您的万无一失的方法。例如,我可以执行以下操作从 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 在堆栈上可能是一条完全合法的路径。

【讨论】:

  • 这还需要 A 和 B 向 CFactory 提供它们的类型吗?如何防止欺骗?
  • @devinb,您可以遍历堆栈以防止欺骗,但这不是万无一失的方法。 .Net 的设计初衷并不是为了让函数知道它的调用者,因此任何让它工作的尝试都会有漏洞。
  • 我正在尝试使用恶意编码人员可以访问我的源代码但无法更改它的范例进行编码。但我认为你是对的,几乎不可能排除所有“坏对象”的可能性
  • 恶意编码器将能够模拟“好路径”的逻辑并使用反射来获取您所有的私有方法和数据,因此尝试使用万无一失没有多大意义方法来确定调用者的类型。
【解决方案4】:

好吧,您可以尝试抓取堆栈跟踪并从那里确定调用者的类型,在我看来这太过分了,而且会很慢。

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

interface IFoo { 
     int Value { get; } 
}

然后您的 DoSomething 方法将如下所示:

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

【讨论】:

    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

        最简单的答案是像使用典型的发送者 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();
        
        }
        

        这似乎有点面向对象的东西。您可能会认为 C 是具有方法的抽象类,并且 A、B 继承自 C 并简单地调用该方法。这将允许您检查基础对象的类型,这不是明显的欺骗。

        出于好奇,你想用这个构造做什么?

        【讨论】:

        • 粗略地说,C 包含 A 的信息和 B 的信息,访问该信息的方法是相同的,但 B 无法访问(或能够访问)A 信息,而 B无法访问A信息....大致。
        • 哇,这让我更加困惑 :P 你能把它作为一个真实世界的例子吗?像 .. 猫和狗的例子什么的?
        • 嗯……也许吧。数据库包含美国间谍和俄罗斯间谍的列表。该方法返回一个列表。当美国人询问时,他们得到美国名单,当俄罗斯人询问时,他们得到俄罗斯名单。
        • 而且我想确保他们无法获取彼此的列表。 =P
        【解决方案8】:

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

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

        【讨论】:

        • 我说的是使用 Matt Murrell 的建议,但至少使用与布置事件相同的约定。即传递您的调用对象,然后传递您的偶数参数
        【解决方案9】:

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

        【讨论】:

        • 假设这些函数太大而不能内联。
        【解决方案10】:

        我可能会觉得我会以不同的方式处理这个问题,但是......

        我假设:

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

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

        从逻辑上讲:如果您可以隐藏 E 类的内容,则很可能通过 DLL 进行分发。

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

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

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

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

        【讨论】:

          【解决方案11】:

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

          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!
                  }
              }
          }
          

          【讨论】:

          • +1 指出这一点,因为在 4.5 中添加调用者信息属性的目的是一个很好的例子,说明了实际上需要这样做的上下文,即使乍一看它似乎违反了良好的设计原则。特别是在编写跟踪、调试或诊断工具时。
          • @BitMask777:是的,另一个有意义的例子是在实现INotifyPropertyChanged时。
          猜你喜欢
          • 2018-02-24
          • 1970-01-01
          • 2013-11-25
          • 1970-01-01
          • 1970-01-01
          • 2020-09-07
          • 1970-01-01
          • 2016-09-06
          相关资源
          最近更新 更多