在C#中模拟C ++宏的正确方法是什么?

时间:2016-12-22 14:25:18

标签: c#

我理解为什么宏不包含在语言中;他们很容易被滥用。然而,我现在又一次偶然发现它们似乎令人满意的情况。就在最近,我正在编写一种记录图像文件的方法:

public void LogImage(Bitmap image, string name)
{
    image.Save(LogRootDirectory + name + ".bmp");
}

随后,我认为使用nameof()功能可以更好地提供有关我正在记录的图像的信息。在我的代码中的几个地方,我的行与此非常相似:

ImageLogger?.LogImage(processedImage, nameof(processedImage));

困扰我的是这种重复性,我更喜欢类似于此的预处理器命令:

#define LOGIMAGE(img) ImageLogger?.LogImage(img, nameof(img));

有没有办法模仿上面的宏行为来更好地简化我的代码?

注意:This问题的不同之处在于作者询问C#中是否存在每处理器定义。我从一开始就明白我知道他们不这样做的断言。我的问题是参考我可能用来代替的技术。我相信这是在明确的答案中提供给我的。

2 个答案:

答案 0 :(得分:1)

如果你在C#中不知道编译时的聪明,那么你就不会滥用表达式树了。

public void LogImage(Expression<Func<Bitmap>> b) {
    var memberExpression = b.Body as MemberExpression;
    if (memberExpression == null) throw new ArgumentException("Must be invoked with a member reference.");
    string name = memberExpression.Member.Name;
    Bitmap bitmap = b.Compile()();
    LogImage(bitmap, name);
}

调用

ImageLogger?.LogImage(() => processedImage);

在调用站点生成表达式树时会产生一些开销,即使从未调用过LogImage,所以虽然这很聪明,但它不会被过度使用。此外,这需要您的位图具有名称(getMyProcessedImage()将无效),如果C#支持它,这正是您对宏的问题({{1无效),除了它不会在运行时之前抛出异常。所以在安全方面也不是那么热。

就个人而言,我只是再次输入名称。我甚至不会使用nameof(getMyProcessedImage())。以变​​量名称记录图像对我来说似乎是一种可疑的做法。如果我 希望明确引用生成位图的源中的位置,我会使用调用者信息:

nameof

简单地调用

public void LogImage(
    Bitmap b, 
    [CallerFilePath] string filePath = "", 
    [CallerLineNumber] int lineNumber = 0
) {
    LogImage(b, $"{Path.GetFileNameWithoutExtension(filePath)}({lineNumber})");
}

当然,这个确实假设你想要为每个位置提供唯一的位图,但是给出名称“logger”似乎是一个合适的假设。

答案 1 :(得分:0)

您可以利用匿名类型将自动生成属性名称的事实。首先定义一个像:

这样的重载
public void LogImage(object obj)
{
    var prop = obj.GetType().GetProperties()[0];
    string name = prop.Name;
    Bitmap bitmap = (Bitmap)prop.GetValue(obj);
    LogImage(bitmap, name);
}

然后当您想要记录图像时,使用匿名类型调用它:

ImageLogger?.LogImage(new { processedImage });

这是对机制的滥用,在生产代码中,为了清楚起见,我可能会倾向于输入两次名称。在许多情况下,这也不是完全符合要求,但在这里我们讨论的是将图像保存到文件中,因此分配小对象和反射的额外成本可以忽略不计。