Is there compile-time access to line numbers in C#? - c#

I'm writing a C# program using Visual Studio 2010 where I want to write out certain events to a log file and include the line number the code was on when that happened.
I've only found two ways of capturing line numbers - CallerLineNumber, which requires .Net 4.5/C#5 (I'm targeting .Net 4) and StackFrame.GetFileLineNumber, which apparently requires a debug build and pdb file to work properly, and I'm producing a release build and no pdb file.
But here's what I don't get - both of the above are run-time solutions, but line numbers are compile-time entities. Why is a runtime solution necessary?
I could type in the correct line number as a literal constant by just looking at the bottom of the screen where it says something like "ln 175" . . .
LogEvent("It happened at line 175");
but the problem with that is that if I edit any code before line 175 my literal might no longer be correct. But the compiler knows the correct line number and I've used programming languages in the past that could just pop in the correct line number as a compile time constant. (e.g., ANSI C and Microsoft C++ support a predefined macro called
_LINE_) Is there any way to get C# to do that? If not are there any solutions to my problem?

CAVEATS: This is NOT an answer to the OP. I know that. But people looking for something similar may find this page.
This is not about .NET 4.
This is still ultimately a run-time solution.
But VS 2015, C#, .NET Core or .NET 4.5 allow:
using System.Runtime.CompilerServices;
using System.Diagnostics;
public static String CurrentLocation(
[CallerFilePath] string file = null,
[CallerLineNumber] int lineNumber = 0,
[CallerMemberName] string method = null)
{
String location = file;
if (lineNumber != 0)
{
location += "(" + lineNumber.ToString() + ")";
}
if (!String.IsNullOrWhiteSpace(method))
{
location += ": " + method;
}
if (!String.IsNullOrWhiteSpace(location))
{
location += ": ";
}
return location;
}
[UPDATED USAGE]
With usage something like:
Console.Error.WriteLine(CurrentLocation() + "some message");
or
Debug.WriteLine(CurrentLocation() + "some message");

No, C# doesn't have a macro preprocessor or any meta programming features, so there are no "Compile time" solutions in the entire language. But there are 3rd party macro languages out there that you can use, if you have to, but of course it complicates the build process, Visual Studio won't just figure out how to built it by itself.
You can even use the C preprocessor if you want it. (assuming MSVC compiler)
cl.exe /TC /P /C /EP something.cs > something.raw.cs
cl.exe is the C compiler
/TC tells the C compiler to treat all files as C sources despite their extensions
/P tells the C compiler to only preprocess the file do not compile it
/C preserves the comments
/EP prevents the compiler from generating #line directives, that the C# compiler wouldn't understand
This will allow you to use #include, #define and #if as well as __FILE__ and __LINE__ in your C# program, but again you have to set up Visual Studio to do this additional compilation step, or use a different build system.

One option would be to use the StackTrace class, like so
[Conditional("DEBUG")]
public static void DebugPrintTrace()
{
StackTrace st = new StackTrace(true);
StackFrame sf = st.GetFrame(1);
Console.WriteLine("Trace "
+ sf.GetFileName() + " "
+ sf.GetMethod().Name + ":"
+ sf.GetFileLineNumber() + "\n");
}

In my opinion, line numbers are the wrong approach. Let me explain:
How unique is line number 175? Even if you find a solution, the next question is: which file was it? And it will repeat: if you know the file, you'll ask yourself: which version of the file was it? And you'll integrate the revision number of your source control system. Once you did that, the question will come up: who was calling that code.
If you're debugging an exception, use a debugger and break on first chance exceptions.
If you're analyzing a performance issue, line numbers don't matter, anything unique will do. Try a memory profiler.
If your methods are too long to identify the problems, refactor your code and make the methods shorter. With short methods, you can use an AOP framework like PostSharp with its logging aspect to achieve at least the CallerMemberName feature. It's available down to .NET 2.0 and it's very easy to add logging and remove logging, not like deleting single lines in your code.

Related

IronPython.Runtime.UnboundNameException: name 'str' is not defined

I'm trying to run an IronPython script in C# but sometimes when I run it I get this error:
IronPython.Runtime.UnboundNameException: name 'str' is not defined
I can't figure out why this is happening; it only happens sometimes; right now it happens when I click a planet in my game to view the report on its abilities.
Here's the script I'm trying to run:
'Mines ' + str(Amount1) + ' minerals each turn (modified by planet value).'
As you can see I'm trying to concatenate some strings, one of which is a stringified Amount1 variable (Amount1 I think is the number 800 but it might be a more complex expression equal to 800, I'd have to check on that), but for some reason I'm getting an error saying the str function is undefined, which makes no sense!
There are other scripts being included before this script runs; if you like I can find those and paste them here in case that might help...
edit: here's the full script that I'm running:
import sys, imp;
import __builtin__ as builtins;
builtins_code = """""";
exec builtins_code in builtins.__dict__;
import clr
clr.AddReference('System.Core')
import System
clr.ImportExtensions(System.Linq)
clr.AddReference('FrEee.Core')
import FrEee
import FrEee.Utility
clr.ImportExtensions(FrEee.Utility.Extensions)
from FrEee.Modding import Mod
from FrEee.Game.Objects.Space import Galaxy
from FrEee.Game.Objects.Civilization import Empire
'Mines ' + str(Amount1) + ' minerals each turn (modified by planet value).'
Could the exec builtins_code line be deleting the str function? If so, how can I make sure that builtins_code gets added to builtins rather than replacing it?
edit2: nope, even if I remove the first four lines, it crashes when I process a turn in a single player game and then click my homeworld!
edit3: if I put the script in a text file and run it as a watch (see below) it runs just fine, even on the breakpoint where it crashes:
FrEee.Modding.ScriptEngine.EvaluateExpression<string>(System.IO.File.ReadAllText("c:/users/edkol/desktop/test.py")), ac
The line:
exec builtins_code in builtins.__dict__
just adds _ to the interactive environment so it repeats the last evaluated variable. Without that line, _ isn't defined. Note that this feature isn't very useful in a script and could be removed. This seems unrelated to your issue.
It looks like the sole interest of IronPython is to be able to interact easily with Microsoft/C# stuff, but it remains "stable" in 2.7 version and it's probably not as bug-free as the official python versions (similar bug was encountered and not solved on reddit, not that it helps, but you know you're not the only one...).
It has to be a bug because you cannot delete a builtin when it's not overridden:
>>> str
<type 'str'>
>>> del str
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'str' is not defined
Now let's be pragmatic: there's no way someone is going to fix that bug in the near future.
So you could try to "restore" the str name by doing:
str = __builtins__.get('str')
I didn't test this, maybe it's not going to work either once you're in this strange state, but there are other workarounds to get hold of "lost" class names, using an instance and __class__ attribute:
>>> "".__class__(34) # "".__class__ is str
'34'
>>> 0 .__class__(1.4) # note the space to help the parser :)
1
The only problem is that is going to look weird in your code. Don't forget to comment!
Anyway, you don't really need str for this, you can workaround your issue by using str.format on a format string (positional):
'Mines {} minerals each turn (modified by planet value).'.format(Amount1)
or by keyword:
'Mines {Amount1} minerals each turn (modified by planet value).'.format(Amount1=Amount1)
That method doesn't involve str built-in. Not saying that you're not going to have other problems, but even when str is available, this is one prefered way to format a string.
Work your way out by avoiding such errors. Fortunately, python exceptions are explicit and can be catched with a nice traceback.

Tracking method "bloat" over time with nDepend

We are looking at using nDepend to start tracking some of our technical debt, particularly around hard to maintain methods and cyclomatic complexity.
I believe this is possibly by taking a baseline report and then running a new analysis to provide the delta. Below is a very basic Powershell I've put together that does this.
$nDepend = "C:\_DEVELOPMENT\nDepend\NDepend.Console.exe"
$targetFile = "C:\_DEVELOPMENT\AssemblyToTest\CodeChallenge.Domain.ndproj"
$projectFolder = Split-Path -Path $targetFile
$outputFolder = "nDepend.Reports"
$previous = ""
Clear-Host
# See if we already have a .ndar file in the output folder, if we do back it up so we can do a comparison
if (Test-Path $projectFolder\$outputFolder\*.ndar)
{
Write-Output "Backing up previous NDAR report"
Copy-Item $projectFolder\$outputFolder\*.ndar $projectFolder\previous.ndar
$previous = ".\previous.ndar"
}
#The output path appears to be relative to the .ndproj file
& $nDepend $targetFile /Silent /OutDir .\$outputFolder /AnalysisResultToCompareWith .\previous.ndar
Here is the rule I've configured in nDepend: -
failif count > 1 bobs
from m in Methods
where m.NbLinesOfCode > 10
where m.WasChanged()
select new { m, m.NbLinesOfCode }
The goal of this is not to break the build if we have methods over 10 lines, but rather to break the build if somebody edits an existing method that is too big and does not improve it (or make it worse). However the where m.WasChanged() part of the rule isn't being triggered regardless of how much code I add. If I comment it out it will alert me that there are plenty of methods that exceed 10 lines, but I only want to know about recently changed ones.
Am I using the rule wrong? Or perhaps my powershell is incorrectly using the /AnalysisResultToCompareWith parameter?
There are default rules like Avoid making complex methods even more complex in the rule group Code Smells Regression that are close to what you want to achieve. You can get inspired by their source code.
The key is to retrieve the methods changed with...
m.IsPresentInBothBuilds() &&
m.CodeWasChanged() &&
and then compare metric evolution since baseline by accessing m.OlderVersion().
A ICompareContext reference two code bases snapshots the newer version and the older version. In this context the OlderVersion() extension method returns actually calls the ICompareContext.OlderVersion(codeElement), from the doc:
Returns the older version of the codeElement object.
If codeElement is already the older version, returns the codeElement object.
If codeElement has been added and has no corresponding older version, returns null.
This method has a constant time complexity.

CSC : error CS7038: Failed to emit module

After installing Visual Studio 2015 and building my project I receive the error
"CSC : error CS7038: Failed to emit module".
However my solution is building fine in Visual Studio 2013.
It is an ASP.NET webforms project .NET 4.0
Anyone?
UPDATE: it looks like the problem has to do with Red Gate Smart Assembly in combination with method parameters with default values.
UPDATE: Smart Assembly 6.9 fixes the error for me.
Original Snippet:
private void radButton1_Click(object sender, EventArgs e)
{
string perp = radTextBox1.Text;
int i = 0;
DataRow arp = ale.Rows[i];
while (i <= ale.Rows.Count)
{
if (ale.Rows[i].Field<>("FullName") = perp)
{
arp = ale.Rows[i];
ale.Rows.Remove(arp);
}
}
i = ale.Rows.Count;
radLabel1.Text = i.ToString();
}
Changed this:
if (ale.Rows[i].Field<>("FullName") = perp)
To This:
if (ale.Rows[i].Field<String>("FullName") == perp)
Got the same error (fresh installation of the VS2015 Enterprise, ASP.NET webforms project .NET 4.0).
After some investigation I've found that there are two DLLs in references which causes this. Both are .Net 2.0 assemblies and both of them obfuscated by Red Gate Smart Assembly 6.5. And the real reason is... obfuscation.
Luckily, these assemblies are mine, so I've tried to build them without using of Smart Assembly - error is gone.
Interesting is that no any errors or warnings shown by Visual Studio before trying to build a project.
Good luck!
EDIT: Updating Smart Assembly to version 6.9 fixed an issue.
As #Andrey reported this does appear to be an issue with obfuscated assemblies that is causing some difficulty for Roslyn. Today I was able to get a live repro of this error and the root cause appears to be the obfuscator is invalidating / corrupting how default parameter values are stored in metadata. When run through ildasm the default values will be displayed as:
.param [3] /* Invalid default value for 0800001F: */
The previous version of the compiler handled this scenario by treating the invalid value as null or default(T). We will be fixing Roslyn to have the same behavior.
I also had this exception being thrown in VB.NET (Visual Studio 2015 - Pro), and isolated a single line that was causing the error.
In the line of code below, if you define model as an integer, as in:
Dim model as Integer = 2
and then use:
Const N As Integer = model
you will throw the exception.
However, when I modified this to:
Dim N As Integer = model
the exception was not thrown. The Const syntax was legacy code from another program that I rapidly added model to, and constants can't be set to pre-defined integer types.
I just had "Failed to emit module". I mistakenly put empty brackets in a call to a generic extension method, only when inside a ternary operator, like this:
var a = b == 6 ? 8 : x.Foo<>();
(Outside of a ternary op, I just get regular error CS7003: Unexpected use of an unbound generic name.)
Extending on the answer from #jony-adamit:
I also had this compilation error being thrown in C# (Visual Studio 2015). It came down to the differences in compiler output from .NET 4.5 and Roslyn the new compiler in VS2015. You can take the below code and run it yourself or use dotnetfiddle (https://dotnetfiddle.net/fa2rMs) and switch between compilers.
In .NET 4.5 compiler (C# compiler version 12.0 or earlier) you get this message:
Compilation error (line 16, col 7): The type or namespace name
'Select' does not exist in the namespace 'Tools.Weapons' (are you
missing an assembly reference?)
In the Roslyn 1.0.0 and 1.1.0 compiler versions you get this message:
Compilation error (line 1, col 1): Failed to emit module
'MyAssembly'.
Code to reproduce the error (notice the namespace):
public class Program
{
public static void Main()
{
Tools.Delegater.Work();
}
}
namespace Tools
{
public static class Delegater
{
public static System.Action Work = () =>
{
var query = from x in Weapons
select x;
};
}
}
namespace Tools.Weapons
{
public class Cannon { }
}
As you can tell from the compiler messages, Roslyn's message leaves you guessing as to the what and the where about the compiler error. And depending on the size of the application, the lack of details could take days or weeks to discover the root cause. Whereas the message in the previous compiler pointed you to the exact spot to start reviewing your code.
Another big difference is the previous compiler will show you a syntax error in Visual Studio for this scenario, but unfortunately Roslyn (atm) does not. However, if you knew where to look and you hovered your mouse over the 'x' variable in the linq to sql statement, then you would see that Rosyln doesn't understand how to interpret it.
I was getting a similar error: "BC36970. Failed to emit module 'Something.dll'." I realize the error code is not the same but it is also a "failed to emit" type issue so I thought I'd share my answer.
Problem: My problem was that I had a string that was a constant but I was trying to append another string to it, like so (using VB code):
Dim MyString1 As String = "Test1"
Const MyString2 As String = "Test2" & MyString1
Solution: I simply had to convert the second string from Const to Dim and the error disappeared:
Dim MyString1 As String = "Test1"
Dim MyString2 As String = "Test2" & MyString1
I got this error when using a generic method but failing to include the type.
This code gave the error:
example.GetValue<>(foo);
Correction simply was to specify the type:
example.GetValue<string>(foo);
I'm not sure if the erroring example is valid syntax or not: I would have expected a syntax error if it is not rather than this error, and Intellisense accepted the syntax.
There's another bug that can cause this exact error:
Happened to me after changing a property name without refactoring, which caused some linq code to call the namespace itself!
To see the bug in action all you have to do is run this piece of code:
public static int Test()
{
var x = from c in ConsoleApplication5
select 3;
return x.First();
}
Where ConsoleApplication5 is the namespace!
This only happens in VS2015.
Update:
There's an issue for this now on GitHub if anyone's interested.
I got this error when I was working with data table and did not provide a data type where it was required:
bool temp = dtTab.AsEnumerable().Any(row => row.Field<>("active") == 1);
Giving it a proper data type got rid of the error. I also had to convert it to string to be able to compare it correctly-
bool temp = dtTab.AsEnumerable().Any(row => row.Field<Boolean>("active").toString() == "1");
I got this error too, seems like there is some major reasons that cause this error.
I posted this answer to wrap up mentioned solutions and help other guys so they can solve this error quickly
Using obfuscated DLLs by Smart Assembly mentioned on marked answer Solution: Update Smart Assembly to version 6.9
Namespace issue mentioned here, also has open Github issue Here
In my case the problem was this one, Field is strongly-typed, not specifying it, mentioned here
Using Const keyword, mentioned here
Please read all answers here on this page carefully, I'm sure you can solve the issue using one of mentioned solutions, good luck :)
This answer is similar to that posted by #wrtsvkrfm
'Dim NEWLINE As String = "<BR/>"
Dim NEWLINE As String = vbCrLf
Const HT_QTC_VENDOR As String = "<B>some stuff {0}</B>" & NEWLINE
This will cause the DLL to be not emitted,
Change the top 2 lines to
'Const NEWLINE As String = "<BR/>"
Const NEWLINE As String = vbCrLf
and the error goes away.
This should be a compilation error.
A CONST clearly cant depend on a variable, which may..., well..., vary. :-)
I get the same "Failed to emit module" error with this code but changing the "Const" to "Dim" resolves the problem:
Dim userStatus1 As String = convAttrSet("user_status")
Dim userStatus2 As String = convAttrSet("user_status")
Const PENDING As Boolean = (userStatus1 = userStatus2)
I got this error when using linq to entities and my linq query had a syntax error in the join clause
For me the issue occured after adding a postbuild step that changes the assemblyinfo.cs (NetRevisionTool.exe).
Take care to not change the AssemblyInfo.cs between building and Edit and continue.
I also had this exception being thrown in VB.NET (Visual Studio 2015 - Pro), and isolated a single line that was causing the error
Faulty....
Dim i As Short
Dim j As Short = BancoCombienElement(LaChaine)
Const a = (22 / 60) * j
Dim TiTxt As String = ""
Dim S_8_10_12_14 As String = ""
Work good!!!!!!!!
Dim i As Short
Dim j As Short = BancoCombienElement(LaChaine)
Dim a As Short = (22 / 60) * j
Dim TiTxt As String = ""
Dim S_8_10_12_14 As String = ""
Reason:
This error seems to be introduced when we have something weird in our code.
For me I made a mistake while getting the current user from identity.
Error Code:
int currentUserId = User.Identity.GetUserId<>();
Fixed Code:
Please specify the data type after GetUserID<---------->();In this case I have specified int.
Note
Please try to undo your code step by step so that you can find this issue. But otherwise it was very difficult for me track that error.
Thanks
This can happen for any error that the compiler did not catch. Probably
either the compiler doesn't know what IL to emit, or
it knows but in the process of actually doing it, something unexpected happens or
the IL that it emits is invalid and some safety net at a lower level than the compiler itself produces an error that the compiler is not built to handle.
In any case, this CS7038 occurs because the C# is valid and there is a good chance the compiler encountered an error in such an unexpected place that it is no longer able to trace it to any source code line that you wrote. Hence the seemingly random errors that people report here to cause this.
I've used C# for decades and I arrive here now because this is the first time ever I get an error like this.
In my case the following code generates the error. Use <LangVersion>Preview</LangVersion> in the project file and use the C# 8 compiler in Visual Studio 2019 preview.
void FailToEmitDll(ReadOnlySpan<char> span) {
ReadOnlySpan<char> temp;
temp = 1 switch
{
_ => span
};
}
Odd enough, if you change the declaration and the assignment to read like var temp instead, all works well.
Adding to the list of possible causes, in VS2019 with C# 8, I got the "Failed to emit module" message when doing something like:
_ = someBool ? SomeVoid() : default;
I filed a bug report about it here: https://github.com/dotnet/roslyn/issues/41741
I got this error when I run a complex expression in Mock object, I fixed this by store the expression result to local variable.

Getting line number of code

Within the code I'm responsible for, I have a few 'throw new Exception()' methods flying around.
The issue is, the catch is not always in the best place or even in the same class. This means when I catch an error, I don't know where the throw originated or even if I do, there could be hundreds/thousands of lines of code in each class.
So, within the throw message, for debugging, it may be useful to state the Class Name, Project Name, Method Name and the line number for easier navigation. The line number is tricky. I can't hard code it because as soon as I amend the code it's unlikely to remain on the same line.
So, my 2 questions are
1) Do we like this idea or think no, there are better approaches!
2) Any ideas how to get the line number?
Have you considered looking at the StackTrace information found under System.Diagnostics? An example can be found at:
http://msdn.microsoft.com/en-us/library/system.diagnostics.stacktrace.aspx
If you include the debugging symbols (.pdb) files, in the same directory as the .dll or .exe, it should load them automatically and provide the line number in the exception.StackTrace.
To build the symbols, in your release build settings, under Advanced Build Settings, turn Debug Info to Full
Look at the System.Diagnostics.StackFrame class for grabbing line numbers. I believe the method GetFileLineNumber may help you out
http://msdn.microsoft.com/en-us/library/system.diagnostics.stackframe
This info is usually available in the stack trace - By Jeremy (see my comments).
Sorry, not sure how to mark a comment as the right answer!?
Line numbers do not show up in code compiled in Release mode. If this is an in-house application you and you really want the line numbers you could always deploy the code compiled in Debug mode and then deploy the PDB's with the assemblies. But there is a performance cost involved in this as well so this is not always the best approach. I am not sure of any better approach at this point though.
If I remember correctly, the Roslyn project gives us a better way to get line numbers but not familiar enough with it to give more details.
You can capture the line number of a caller using C# 5.0 feature combined with default parameters. So instead of constructing and throwing the exception directly, make a method that constructs your exception.
Exception CreateMyException(
[CallerFilePath] string filePath = "",
[CallerMemberName] string memberName = "",
[CallerLineNumber] int lineNumber = 0)
{
return new Exception(string.Format("Exception thrown from line {0} in member {1} in file {2}",
lineNumber, memberName, filePath));
}
...elsewhere in your code...
throw CreateMyException(); // compiler injects current values for defaulted arguments.

Getting the Windows System Error Code title/description from its hex number

I'm messing around with some windows functions using p/invoke. Occasionally, I get an error code that is not ERROR_SUCCESS (such an odd name).
Is there a way to look these up within the program? Forexample, if I get error 1017. Can I tell the user
The system has attempted to load or
restore a file into the registry, but
the specified file is not in a
registry file format.
(ERROR_NOT_REGISTRY_FILE: 0x3F9)
Instead of
Error Code: 1017
I'm not sure if there's a niifty .NET wrapper, but you could call the FormatMessage API using P/Invoke.
See this answer for how it would normally be called from native code. Though the question refers to grabbing error codes from HRESULTs, the answer also applies for retreiving codes from the regular OS error codes coming from GetLastError/GetLastWin32Error).
EDIT: Thanks Malfist for pointing me to pinvoke.net, which includes alternative, managed API:
using System.ComponentModel;
string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
Console.WriteLine(errorMessage);
You could take the defines from winerror.h at Rensselaer Polytechnic Institute, and put them into an Enum:
public enum Win32ErrorCode : long
{
ERROR_SUCCESS = 0L,
NO_ERROR = 0L,
ERROR_INVALID_FUNCTION = 1L,
ERROR_FILE_NOT_FOUND = 2L,
ERROR_PATH_NOT_FOUND = 3L,
ERROR_TOO_MANY_OPEN_FILES = 4L,
ERROR_ACCESS_DENIED = 5L,
etc.
}
Then if your error code is in a variable error_code you would use :
Enum.GetName(typeof(Win32ErrorCode), error_code);
I landed on this page while in search of a managed alternative to calling FormatMessage through P/Invoke.
As others have said, there is no way to get those capitalized, underscored names, short of looking them up in winerror.h, which I have seen reproduced online in various places where I landed in the course of searching for information about resolving specific status codes. A quick Google search, for winerror.h, itself, uncovered a page, at Rensselaer Polytechnic Instutute, where someone has helpfully extracted the #define statements from it.
Looking at it gave me an idea; I think there may be a way to get there, working from the source code of winerror.h, which I have, as part of the Windows Platform SDK that ships with every recent version of Microsoft Visual Studio.
Right now, I am in the middle of sorting out a pressing issue in the .NET assembly that brought me to this page. Then, I'll see what I can cobble together; this kind of challenge is right up my alley, and somebody threw down a gauntlet.
Yes there's a function that does that but I don't remember what it is. In the mean time, you can use the error lookup tool (Tools->Error Lookup) to see what a particular code means from within Visual Studio.

Categories

Resources