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

14

无论这是否是一个好主意,是否有可能实现一个接口,使得正在执行的函数知道其调用对象的类型?

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

在我看来,这似乎是一种不好的编码实践(因为参数没有改变,但输出会改变),但除此之外,是否有可能呢?

我面临这样的情况,希望几个特定的类型能够调用某个函数,但我不能排除对该函数的访问。 我想到了一个“type”参数。

DoSomething(int input, Type callingobject)

但无法保证调用对象使用GetType(this)而不是GetType(B)来欺骗一个B,而不考虑它们自己的类型。

这是否就像检查调用堆栈一样简单(相对简单)?


你能提供更多的背景信息吗?因为现在我不明白为什么你不只是在C中实现单独的方法。像C.DoSomethingA和C.DoSomethingB这样的东西。或者更好的办法是将行为放入A和B类中。 - Boris Callens
1
简而言之,我最终会有30或40个完全相同的函数,只是差了一两行。 - DevinB
@devinb:请看我在下面帖子中所做的编辑。你可能需要考虑开一个不同的问题。 - John Feminella
1
我认为你是对的,然而,我的想法遭受了大量(非常友好和专业的)批评,这意味着我预期的重新设计将比我想象的要重大得多。因此,我暂时无法简洁地提出更好的问题。 - DevinB
@devinb:嗯,我希望我们没有太苛刻!:) 但是一旦你确定了计划,请跟进 - 我很想听听细节。 - John Feminella
11个回答

18

首先,是的,这样做是一个可怕的想法,违反了所有坚实的设计原则。如果有其他可行的方法,你应该考虑另一种方法,比如简单地使用多态性——这似乎可以重构为一个相当明显的单分派情况。

其次,是的,这是可能的。使用 System.Diagnostics.StackTrace 来遍历堆栈;然后获取适当的上一层级别的 StackFrame。然后通过在该StackFrame 上调用GetMethod()来确定哪个方法是调用者。请注意,构建堆栈跟踪是一个潜在的昂贵操作,而且调用者可能会掩盖事情真正发生的地方。


编辑:这位OP的评论非常清楚,这可能可以成为一个通用方法或多态方法。 @devinb,您可能希望考虑提出一个新问题,详细说明您要做什么,我们可以看看它是否适合一个好的解决方案。

简而言之,我最终会有30或40个完全相同的函数,只是差了一两行。 - devinb(12秒前)


糟糕!我删掉了我的“重复答案”……太愚蠢了,手指太慢了……顺便加一分。 - Binary Worrier
1
没问题 - 我经常被那个可恶的Jon Skeet抢先了。顺便说一句,除非你的帖子几乎完全重复,否则我不会删除它。如果你提出的观点与我的大致相似但在某些方面有所不同,一定要保留它。 - John Feminella

14

作为另一种方法,您是否考虑根据请求类的对象类型提供不同的类。比如以下代码:

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
@devinb,你可以遍历堆栈来防止欺骗,但这并不是一种百分之百可靠的方法。.Net并没有设计一个函数能够知道它的调用者,因此任何试图让它工作的尝试都会有漏洞。 - JaredPar
我试图使用这种编程范式,即恶意编码者将可以访问我的源代码,但无法更改它。但我认为你是对的,排除所有“badObject”可能性几乎是不可能的。 - DevinB
恶意编码人员可以模拟“好路径”的逻辑,并使用反射获取所有私有方法和数据,因此尝试使用一种百分之百可靠的方法来确定调用者的类型并没有多大意义。 - Yaur

4

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

+1 是因为指出这一点非常重要,因为在4.5中添加调用者信息属性的目的是一个很好的例子,即使乍一看似乎违反了良好的设计原则。具体来说,当编写跟踪、调试或诊断工具时需要这样做。 - BitMask777
1
@BitMask777:是的,另一个适用情况是在实现INotifyPropertyChanged时。 - Olivier Jacot-Descombes

2
最简单的答案是像传递典型的发送器(sender)和事件参数(eventargs)一样来传递发送器对象(sender object)。您的调用代码应该如下所示:
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 的信息……大致就是这样。 - DevinB
1
哇,这让我更加困惑了:P 你能给一个现实世界的例子吗?比如猫狗之类的例子? - Boris Callens
1
嗯...也许吧。 一个数据库包含了美国间谍名单和俄罗斯间谍名单。该方法返回一个列表。当美国人询问时,他们会得到美国名单;当俄罗斯人询问时,他们会得到俄罗斯名单。 - DevinB
我想确保他们不能获取彼此的列表。 =P - DevinB

2
由于运行时可能进行内联操作,因此不能保证可靠性。

假设这些函数太大而无法内联。 - DevinB

0

你可以尝试获取堆栈跟踪并从中确定调用者的类型,但在我看来这是一种过度设计且速度较慢的方法。

那么,如何使用一个接口来实现 A,B 呢?

interface IFoo { 
     int Value { get; } 
}

然后你的DoSomething方法会像这样:

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

0

像事件处理程序一样构建它,我敢肯定FX cop甚至会建议您这样做。

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

我建议使用Matt Murrell提出的方法,但至少要按照事件布局相同的约定。即先传递调用对象,然后是事件参数。 - dfasdljkhfaskldjhfasklhf

0

我可能会陷入麻烦,因为我认为我会以不同的方式处理这个问题,但是...

我假设:

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

你知道并创建了这三个类,并且可以将类E的内容隐藏起来,不对外公开。如果不是这种情况,你的控制权将永远失去。

逻辑上讲:如果你可以隐藏类E的内容,那么你很可能通过分发DLL来实现。

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

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

让基类封装如何正确调用E中的方法的知识将是完美的面向对象编程实现,我认为

然而...我可能忽略了这里的复杂性。


0

几乎总是有一个合适的设计来完成你需要的工作。如果你退后一步,描述你实际需要做什么,我相信你至少会得到一个好的设计,而不需要诸如此类的事情。


我的设计非常紧密耦合且自我引用,所以是的,我已经想出了一些不会有这种特定丑陋的设计,但它们有它们自己的丑陋。我只是尝试探索每个选项。 - DevinB
@devinb 我觉得其他选项不可能像这个一样丑。 - Rex M
它们更加复杂。相对于这个相当简单,但是很糟糕。=D 我只是好奇而已。 - DevinB

0
你可以使用 System.Diagnostics.StackTrace 类来创建堆栈跟踪。然后,你可以查找与调用者相关联的 StackFrameStackFrame 有一个 Method 属性,你可以使用它来获取调用者的类型。
但是,上述方法不应在性能关键代码中使用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接