简体   繁体   中英

What's the proper way to emulate C++ macros in C#

I understand why macros were not included in the language; that they can easily become abused. However I now and again stumble upon situations where they seem desirable. Just recently I was writing a method to log image files:

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

Subsequently, I thought it clever to utilize the nameof() feature to better provide information about the image I am logging. In several places in my code I have lines very similar to this:

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

What bothers me is the repetitiveness of this and I would much prefer a pre-processor command similar to this:

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

Is there a way to emulate the above macro behaviour to better streamline my code?

Note: This question differs in that the author is asking if per-processor definitions exist in C#. An assertion that I made clear from the beginning that I know that they do not. My question is in reference to techniques that I may use in substitution. Which I believe was provided to me in the marked answer.

If you're at a loss for compile-time cleverness in C#, you're just not abusing expression trees hard enough.

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

Invoked as

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

There is a bit of overhead associated with producing the expression tree at the call site, even if LogImage is never called, so while this is clever, it's not something to be overused. Also, this requires that your bitmap has a name ( getMyProcessedImage() will not work), which is exactly the problem that you would also have with a macro, if C# supported them ( nameof(getMyProcessedImage()) is invalid), except that it won't throw an exception until runtime. So it's not so hot in terms of safety either.

Personally, I'd just type the name again. I wouldn't even use nameof , really. Logging images under variable names seems like a dubious practice to me. If I did want a clear reference to the place in the source where the bitmap was produced, I'd use caller info:

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

Invoke simply as

ImageLogger?.LogImage(processedImage);

Of course, this does assume you want unique bitmaps for each location, but given the name "logger" that seems an appropriate assumption.

You can leverage the fact that anonymous types will have automatically generated property names. First define an overload like:

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

then when you want to log an image call it using an anonymous type:

ImageLogger?.LogImage(new { processedImage });

This is an abuse of a mechanism and in production code I'd probably lean towards typing out the name twice for the sake of clarity. Also this is not exactly performant in many cases, but here we are talking about saving an image to a file, so the extra cost of allocating a small object and reflection are negligible.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM