I've written a simple class for debugging and I call the method Debugger.WriteLine(...) in my code like this:
Debugger.WriteLine("[Draw]", "InProgress",
"[x,y] = " + x.ToString("0.00") +
", " + y.ToString("0.00") +
"; pos = " + lastPosX.ToString() + "x" +
lastPosY.ToString() + " -> " + posX.ToString() +
"x" + posY.ToString() + "; SS = " +
squareSize.ToString() + "; MST = " +
startTime.ToString("0.000") + "; Time = " + time.ToString() +
phase.ToString(".0000") + "; progress = " +
progress.ToString("0.000") + "; step = " +
step.ToString() + "; TimeMovementEnd = " +
UI.MovementEndTime.ToString()
);
The body of the procedure Debugger.WriteLine is compiled only in Debug mode (directives #if, #endif). What makes me worry is that I often need ToString() in Debugger.WriteLine call which is costly because it creates still new strings (for changing number for example). How to solve this problem?
A few points/questions about debugging/tracing:
I don't want to wrap every Debugger.WriteLine in an IF statement or to use preprocessor directives in order to leave out debugging methods because it would inevitable lead to a not very readable code and it requires too much typing.
I don't want to use any framework for tracing/debugging. I want to try to program it myself.
Are Trace methods left out if compiling in release mode? If it is so is it possible that my methods would behave similarly?
With the static String.Format method I can do this:
output = String.Format("You are now {0} years old.", years);
Which seems nice. Is it a solution for my problem with ToString()?
Using Reflector I found out that Debug.Writeline is declared this way :
[Conditional("DEBUG")]
public static void WriteLine(string message)
That means that in Release mode all calls to this method are eliminated from code.
For example this code :
public static void Test(int a)
{
string b = Console.ReadLine();
Debug.WriteLine(a + " " + b);
Console.WriteLine(a + " " + b);
}
compiles in release mode to :
public static void Test(int a)
{
Console.WriteLine(a + " " + Console.ReadLine());
}
Use StringBuilder to create your output strings instead of concatenating each and every value.
And you can create your own custom debugger (MyDbg) class that contains a WriteLine member the contents of which you can surround with compile directives. It wouldn't entirely compile out the debug code but would turn you MyDbg.WriteLine calls into no-ops.
Here's a quick sketch of the class:
using System;
using System.Text ;
public static class MyDbg
{
public static void WriteLine(string str) // and some other params
{
#if DEBUG
StringBuilder sb = new StringBuilder();
sb.Append(str);
// etc. appending other params as you see fit
#endif
}
}
OR
[Conditional("DEBUG")]
public static class MyDbg
{
public static void WriteLine(string str) // and some other params
{
StringBuilder sb = new StringBuilder();
sb.Append(str);
// etc. appending other params as you see fit
}
}
You'd modify it to suit your own needs of course. And instead of creating a separate class, you could create a member method if #if DEBUG/#endif built-in for displaying its own state when in the debugger.
For Logging have a look at frameworks such as Log4net or the Enterprise library. They make logging configurable in many ways. Even if you want to log at all.
HTH
The pattern I use is to define a logging interface (ILogger) that gets dependency injected into all the classes
public class MyClassThatLogs
{
public MyClassThatLogs(ILogger logger)
{
try
{
logger.Write("bla bla bla");
}
catch(Exception ex)
{
logger.Write(ex); //overload of Write() that takes in an Exception
}
}
}
So when you new it up you can pass in a 'real' logger or a mock/fake one
var mc = new MyClassThatLogs(new FakeLogger());
Now you have abstracted away the need to know in the MyClassThatLogs class what's going on with logging. Basic SOLID stuff. So the 'top' (say main() method of a console) would have the IF DEBUG directive then pass in the correct implementation of the logger.
You do not need to wrap every line. Just write helper method containing Debug.WriteLine and turn it on/off by flag (usually bool).
Tracing will be in your code even in release mode. You can configure it, see http://msdn.microsoft.com/en-us/library/system.diagnostics.tracelistener.aspx
string.Format calls ToString() on arguments internally, so no benefit there.
Related
My code is passing the wrong argument to a function for some reason.
I have a static class say class A having this function AddMaster :
public static void AddMaster(string ipAddress, int port, List<RegisterMap> registers)
{
// THIS LINE PRINTS THE ACTUAL VALUES SENT FROM THE CALLER FUNCTION
System.IO.File.AppendAllText("datalog_MB.txt", ipAddress + " " + registers[0].FriendlyName + "\n");
new Thread(() =>
{
_tasks.Add(Task.Factory.StartNew(() =>
{
Monitor.Enter(_masters);
_masters.Add(new Master().Connect(ipAddress, port).SetRegisters(registers));
_masters.Last().OnEvent += MasterEvent;
Debug.WriteLine(_masters.Count + " TCP masters connected");
Monitor.Exit(_masters);
}));
}).Start();
}
I have another non-static class Master having the function SetRegisters:
public Master SetRegisters(List<RegisterMap> registerList)
{
// HERE THE FriendlyName ALWAYS PRINTS THE VALUE OF THE LAST FUNCTION CALL
System.IO.File.AppendAllText("datalog_MB_1.txt", _hostname + " " + registerList[0].FriendlyName + "\n");
_registersToRead = registerList;
return this;
}
The function AddMaster() is called in a loop.
The first code logs the following which is correct:
# datalog_MB.txt
192.168.0.12 192.168.0.12:Value 1
192.168.0.11 192.168.0.11:Value 1
However the second code block prints the following ( See the second value has changed ):
# datalog_MB_1.txt
192.168.0.12 192.168.0.11:Value 1
192.168.0.11 192.168.0.11:Value 1
Edit #1
foreach (var equipment in MSSQL.GetEquipments(true))
{
registers.Clear();
System.IO.File.AppendAllText("dataeq.txt", equipment.IPAddress + " " + equipment.Name + " " + equipment.ID + "\n");
try
{
registers.Add(
new RegisterMap
{
FriendlyName = equipment.IPAddress + ":Value 1",
Register = 2001,
Type = RegisterType.HoldingRegister,
StationID = 1
});
registers.Add(
new RegisterMap
{
FriendlyName = equipment.IPAddress + ":Value 2",
Register = 2002,
Type = RegisterType.HoldingRegister,
StationID = 1
});
A.AddMaster(equipment.IPAddress, 502, registers);
var json = new JavaScriptSerializer().Serialize(registers);
System.IO.File.AppendAllText("data_reg.txt", json + "\n\n");
}
catch(Exception err)
{
System.Windows.MessageBox.Show(err.Message);
}
}
Edit #2*
Fiddle: https://dotnetfiddle.net/h3yn7p
Any idea what might be going wrong?
You should not Clear the registers - recreate it:
List<RegisterMap> registers = null;
foreach(var equipment in equipments)
{
registers = new List<RegisterMap>();
....
}
Otherwise you are processing the same instance of List<RegisterMap> in parallel in multiple threads while manipulating it (i.e. thread which has the foreach(var equipment in equipments) loop will run and create all the threads and start them), which can lead to a lot of different concurrency issues.
Also some notes:
Consider using concurrent collections, for example ConcurrentBag<T> instead of Monitor + List (cause in current code it makes Thread/Task handling pointless)
Based on the provided code there is no need to create Thread - tasks should be enough. In modern .NET there is rarely a case when you need to create Threads manually, most of the time it is hugely discouraged (read more).
I have this kind of code, with purpose is to wrap UnityEngine.Debug.Log so I can disable them all on production also so that I can look/filter up later.
using System;
public enum LogType
{
DEBUG,
CRITICAL
}
public class LogHelper
{
public static void Log(LogType lt, string format, params object[] objs)
{
if (lt == LogType.CRITICAL)
{
// StackTrace st = new StackTrace(new StackFrame(true));
// Console.WriteLine(" Stack trace for current level: {0}", st.ToString());
// StackFrame sf = st.GetFrame(0);
// Console.WriteLine(" File: {0}", sf.GetFileName());
// Console.WriteLine(" Method: {0}", sf.GetMethod().Name);
// Console.WriteLine(" Line Number: {0}", sf.GetFileLineNumber());
// Console.WriteLine(" Column Number: {0}", sf.GetFileColumnNumber());
}
// TODO: write to /tmp file too
UnityEngine.Debug.Log("[" + lt + "] " + String.Format(format, objs));
}
public static void Critical(string format, params object[] objs)
{
Log(LogType.CRITICAL,format, objs);
}
public static void Debug(string format, params object[] objs)
{
Log(LogType.DEBUG,format, objs);
}
}
The problem is, when i call those LogHelper.Debug("something"), the Unity Editor's Log when double clicked will go to that code (one that calls UnityEngine.Debug.Log) instead of the source that call that LogHelper.Debug. How to make it show the caller instead of the LogHelper when I doubleclick the log?
I am not sure, but try:
public static class MyDebug{
public static delegate void TestDelegate(object message);
#if (NOEDITOR)
public static TestDelegate Log =(x)=>{};
#else
public static TestDelegate Log = Debug.Log;
#endif
}
then, control this defining NOEDITOR
One possible solution is to use Editor Console Pro from the Unity Asset Store.
Here are some of the listed features that are relevant to your question (emphasis mine):
See the source code surrounding each method call in the stack, allowing you to see and jump to the code around the log.
Open your code editor to any method or line in a log's stack by clicking on it, rather than just to the Debug.Log call.
[...]
Ignore custom Debug.Log classes in the stack, so double clicking never takes you to the wrong code.
However, it's not a free package (though the price is reasonable and it has excellent reviews).
You could also write your own Unity editor extension to implement something similar to Editor Console Pro using UnityEngine.Application.logMessageReceivedThreaded.
//U can use "LogPlayerBuildError" for this
/// ================================
/// Checks if the object of type T is null or not,
/// If object is null, prints a log message if 'enableLog' set to 'true'
/// https://answers.unity.com/questions/238229/debugconsole-console-clicking.html
/// ================================
public static bool IsNull<T>( this T classType, bool enableLog = true, MonoBehaviour monoInstance = null) where T : class
{
if(classType == null)
{ if(enableLog)
{ //if(classType.GetType().IsSubclassOf(typeof(MonoBehaviour)))
var frame = new System.Diagnostics.StackTrace(true).GetFrame(1);
string fileName = FormatFileName(frame.GetFileName());
int lineNum = frame.GetFileLineNumber();
int colomn = frame.GetFileColumnNumber();
string msg = "WARNING:- The instance of type " + typeof(T) + " is null!"
+ "\nFILE: " + fileName + " LINE: " + lineNum;
//Debug.LogWarning(msg, (UnityEngine.Object)monoInstance.gameObject);
var mUnityLog = typeof(UnityEngine.Debug).GetMethod("LogPlayerBuildError", BindingFlags.NonPublic | BindingFlags.Static);
mUnityLog.Invoke(null, new object[] { msg, fileName, lineNum, colomn });
}
return true;
}
return false;
}
Create a .dll of your extended Log classes and remove the source .cs files from the project folder. This will cause Unity to take you to the original code line rather than the helper class when double clicking in the default Unity console.
As outlined by user on Unity Forums here: Hide Method in Stack Trace Log
For a simple "how to .dll" you can follow this tutorial: Unity and Dlls
Recently had to solve this as was having the same frustration with Unity taking me to helper class rather than the source line. Old Post but still high in search engines so hopefully will help someone out.
Here is an example of what I want to do:
MessageBox.Show("Error line number " + CurrentLineNumber);
In the code above the CurrentLineNumber, should be the line number in the source code of this piece of code.
How can I do that?
In .NET 4.5 / C# 5, you can get the compiler to do this work for you, by writing a utility method that uses the new caller attributes:
using System.Runtime.CompilerServices;
static void SomeMethodSomewhere()
{
ShowMessage("Boo");
}
...
static void ShowMessage(string message,
[CallerLineNumber] int lineNumber = 0,
[CallerMemberName] string caller = null)
{
MessageBox.Show(message + " at line " + lineNumber + " (" + caller + ")");
}
This will display, for example:
Boo at line 39 (SomeMethodSomewhere)
There's also [CallerFilePath] which tells you the path of the original code file.
Use the StackFrame.GetFileLineNumber method, for example:
private static void ReportError(string message)
{
StackFrame callStack = new StackFrame(1, true);
MessageBox.Show("Error: " + message + ", File: " + callStack.GetFileName()
+ ", Line: " + callStack.GetFileLineNumber());
}
See Scott Hanselman's Blog entry for more information.
[Edit: Added the following]
For those using .Net 4.5 or later, consider the CallerFilePath, CallerMethodName and CallerLineNumber attributes in the System.Runtime.CompilerServices namespace. For example:
public void TraceMessage(string message,
[CallerMemberName] string callingMethod = "",
[CallerFilePath] string callingFilePath = "",
[CallerLineNumber] int callingFileLineNumber = 0)
{
// Write out message
}
The arguments must be string for CallerMemberName and CallerFilePath and an int for CallerLineNumber and must have a default value. Specifying these attributes on method parameters instructs the compiler to insert the appropriate value in the calling code at compile time, meaning it works through obfuscation. See Caller Information for more information.
I prefer one liners so:
int lineNumber = (new System.Diagnostics.StackFrame(0, true)).GetFileLineNumber();
In .NET 4.5 you can get the line number by creating the function:
static int LineNumber([System.Runtime.CompilerServices.CallerLineNumber] int lineNumber = 0)
{
return lineNumber;
}
Then each time you call LineNumber() you will have the current line. This has the advantage over any solution using the StackTrace that it should work in both debug and release.
So taking the original request of what is required, it would become:
MessageBox.Show("Error enter code here line number " + LineNumber());
This is building on the excellent answer by Marc Gravell.
For those who need a .NET 4.0+ method solution:
using System;
using System.IO;
using System.Diagnostics;
public static void Log(string message) {
StackFrame stackFrame = new System.Diagnostics.StackTrace(1).GetFrame(1);
string fileName = stackFrame.GetFileName();
string methodName = stackFrame.GetMethod().ToString();
int lineNumber = stackFrame.GetFileLineNumber();
Console.WriteLine("{0}({1}:{2})\n{3}", methodName, Path.GetFileName(fileName), lineNumber, message);
}
How to call:
void Test() {
Log("Look here!");
}
Output:
Void Test()(FILENAME.cs:104)
Look here!
Change the Console.WriteLine format how you like!
If its in a try catch block use this.
try
{
//Do something
}
catch (Exception ex)
{
System.Diagnostics.StackTrace trace = new System.Diagnostics.StackTrace(ex, true);
Console.WriteLine("Line: " + trace.GetFrame(0).GetFileLineNumber());
}
You only asked about the line number and with nullable project type, you then need to use a something like this
internal class Utils
{
public static int Line([CallerLineNumber] int? lineNumber =null)=>lineNumber;
}
in your code, if you like to get a line number you then just call
var line=Utils.Line();
if you are logging and you would like to document the line number in say logging than call the method like this
public void MyMethod(int someValue)
{
switch(someValue)
{
case 1:
if(abc<xyz)
{
logger.LogInformation("case value {someValue} this line {line} was true", someValue ,Utils.Line()-2);
}
break;
case 2:
logger.LogInformation("case value {someValue} this {line} was executed",someValue,Utils.Line());
break;
caste 3:
logger.LogInformation("case value {someValue} this {line} was executed",someValue,Utils.Line());
break;
}
}
You can extend this pattern with any of the other [CallerXXX] methods and not use them where ever, not just in the method parameters.
in the Nuget Package Walter I use a super cool class named ExceptionObject
if you import the NuGet package you have some nice extension methods on the Exception class as well as access to a CallStack showing the call chain including method parameters and parameter values of all methods called.
It's like a stack of an exception only with values showing how you got where you got with what values.
public void MyMethod()
{
try
{
//get me all methods, signatures, parameters line numbers file names etc used as well as assembly info of all assemblies used for documentation of how the code got here
var stack= new CallStack();
foreach( var frame in StackedFrames)
{
logger.LogDebug(frame.ToString());
}
}
catch(SqlException e)
{
var ex = new ExceptionObject(e);
logger.LogException(e,"{this} exception due to {message} {server} {procedure} TSQL-line:{sqlline}\n{TSQL}"
,e.GetType().Name
,e.Message
,ex.SqlServer
,ex.SqlProcedureName
,ex.SqlLineNumber
,ex.Tsql
,ex.CallStack);
}
catch(Exception e)
{
var ex = new ExceptionObject(e);
logger.LogException(e,"{this} exception due to {message} signature: signature}\nCallStack:", e.GetType().Name,e.Message,ex.Signature,ex.CallStack);
}
}
I have an HtmlHelper extension method that takes javascript callback functions as parameters.. for example:
#Html.SomethingCool("containerName", "jsCallbackFunction")
<script type="javascript">
function jsCallbackFunction(e, i) {
alert(e.target.name + ' / ' + i);
}
</script>
As you can see, the javascript callback function name is passed to the HtmlHelper extension method. This causes the developer to have to refer back to the documentation to figure out what parameters the jsCallbackFunction function needs.
I would much rather prefer something like this:
#Html.SomethingCool("containerName", New SomethingCoolCallbackDelegate(Address Of jsCallbackFunction))
<OutputAsJavascript>
Private Sub jsCallbackFunction(e, i)
' SOMETHING goes here. some kind of html dom calls or ???
End Sub
The SomethingCoolCallbackDelegate would provide the code contract for the target function.
Then the compiler would compile the jsCallbackFunction as javascript on the MVC page.
Is there anything like this built into .NET 4 / ASP.NET MVC 4 / Razor 2 ? Or any other technology that can achieve something similar?
Examples are in VB, but solutions in C# are quite acceptable as well.
Clarification:
#gideon: notice that jsCallbackFunction takes two parameters e, and i. However, the HtmlHelper extension method simply asks for a string (the name of the javascript callback function) and does not indicate what parameters this function might take. The problem I am trying to solve is two-fold.
First, the missing parameter hints. A .NET delegate type passed in place of the "javascript callback name" string would accomplish this. I am open to other solutions to accomplish this. I am aware of XML comments. They are not really a solution.
Second, trying to keep the page programmer working in a single language. Switching between javascript and VB (or js and C#) requires (for me at least) an expensive context switch. My brain doesn't make the transition quickly. Keeping me working in VB or C# is more productive and cost effective. So being able to write a function in a .NET language and have it compiled to javascript, in the context of an ASP.NET MVC/razor view, is what I am after here.
#TyreeJackson: SomethingCool is an HtmlHelper extension method that I would write that outputs html and javascript. Part of the javascript output needs to call into a user(programmer)-supplied function to make some decisions. Think of it similar to the success or failure function you supply to an ajax call.
While I can't give you a full transpiler/compiler option since that would be an enormous amount of work, I can suggest the following to assist with the intellisense support and emitting of the functions and calls.
Here is the infrastructure code. You would need to complete the getArgumentLiteral and getConstantFromArgument functions to handle other cases you come up with, but this is a decent starting point.
public abstract class JavascriptFunction<TFunction, TDelegate> where TFunction : JavascriptFunction<TFunction, TDelegate>, new()
{
private static TFunction instance = new TFunction();
private static string name = typeof(TFunction).Name;
private string functionBody;
protected JavascriptFunction(string functionBody) { this.functionBody = functionBody; }
public static string Call(Expression<Action<TDelegate>> func)
{
return instance.EmitFunctionCall(func);
}
public static string EmitFunction()
{
return "function " + name + "(" + extractParameterNames() + ")\r\n{\r\n " + instance.functionBody.Replace("\n", "\n ") + "\r\n}\r\n";
}
private string EmitFunctionCall(Expression<Action<TDelegate>> func)
{
return name + "(" + this.extractArgumentValues(((InvocationExpression) func.Body).Arguments) + ");";
}
private string extractArgumentValues(System.Collections.ObjectModel.ReadOnlyCollection<Expression> arguments)
{
System.Text.StringBuilder returnString = new System.Text.StringBuilder();
string commaOrBlank = "";
foreach(var argument in arguments)
{
returnString.Append(commaOrBlank + this.getArgumentLiteral(argument));
commaOrBlank = ", ";
}
return returnString.ToString();
}
private string getArgumentLiteral(Expression argument)
{
if (argument.NodeType == ExpressionType.Constant) return this.getConstantFromArgument((ConstantExpression) argument);
else return argument.ToString();
}
private string getConstantFromArgument(ConstantExpression constantExpression)
{
if (constantExpression.Type == typeof(String)) return "'" + constantExpression.Value.ToString().Replace("'", "\\'") + "'";
if (constantExpression.Type == typeof(Boolean)) return constantExpression.Value.ToString().ToLower();
return constantExpression.Value.ToString();
}
private static string extractParameterNames()
{
System.Text.StringBuilder returnString = new System.Text.StringBuilder();
string commaOrBlank = "";
MethodInfo method = typeof(TDelegate).GetMethod("Invoke");
foreach (ParameterInfo param in method.GetParameters())
{
returnString.Append(commaOrBlank + param.Name);
commaOrBlank = ", ";
}
return returnString.ToString();
}
}
public abstract class CoreJSFunction<TFunction, TDelegate> : JavascriptFunction<TFunction, TDelegate>
where TFunction : CoreJSFunction<TFunction, TDelegate>, new()
{
protected CoreJSFunction() : base(null) {}
}
Here is an example of a standard function support wrapper:
public class alert : CoreJSFunction<alert, alert.signature>
{
public delegate void signature(string message);
}
Here are a couple of example Javascript function support wrappers:
public class hello : JavascriptFunction<hello, hello.signature>
{
public delegate void signature(string world, bool goodByeToo);
public hello() : base(#"return 'Hello ' + world + (goodByeToo ? '. And good bye too!' : ''") {}
}
public class bye : JavascriptFunction<bye, bye.signature>
{
public delegate void signature(string friends, bool bestOfLuck);
public bye() : base(#"return 'Bye ' + friends + (bestOfLuck ? '. And best of luck!' : ''") {}
}
And here is a console app demonstrating its use:
public class TestJavascriptFunctions
{
static void Main()
{
// TODO: Get javascript functions to emit to the client side somehow instead of writing them to the console
Console.WriteLine(hello.EmitFunction() + bye.EmitFunction());
// TODO: output calls to javascript function to the client side somehow instead of writing them to the console
Console.WriteLine(hello.Call(func=>func("Earth", false)));
Console.WriteLine(bye.Call(func=>func("Jane and John", true)));
Console.WriteLine(alert.Call(func=>func("Hello World!")));
Console.ReadKey();
}
}
And here is the output from the console app:
function hello(world, goodByeToo)
{
return 'Hello ' + world + (goodByeToo ? '. And good bye too!' : ''
}
function bye(friends, bestOfLuck)
{
return 'Bye ' + friends + (bestOfLuck ? '. And best of luck!' : ''
}
hello('Earth', false);
bye('Jane and John', true);
alert('Hello World!');
UPDATE:
You may also want to check out the JSIL. I'm not affiliated with the project and cannot speak to it's stability, accuracy nor efficacy, but it sounds interesting, and may be able to help you.
Here is my SignalR implementation test(please read the comments in the question):
ChatHub Class:
Public Class ChatHub : Inherits Hub
Public Sub MyTest(ByVal message As String)
Clients.All.clientFuncTest("Hello from here, your message is: " + message)
End Sub
End Class
Client Side:
$(function () {
// Reference the auto-generated proxy for the hub.
var chat = $.connection.chatHub;
//reference to Clients.All.clientFuncTest
chat.client.clientFuncTest = function(messageFromServer){
alert(messageFromServer);
}
// Start the connection.
$.connection.hub.start().done(function () {
//Reference to Public Sub MyTest
chat.server.myTest("this is a test");
});
});
This produce the following output in my site:
It's not quite the same thing but WebMethod and PageMethod attributes can help this become much more manageable IMHO.
See also how to call an ASP.NET c# method using javascript
You could also use a WebBrowser control to create your object implementations, then this becomes just like Node.js for the most part.
See also Read Javascript variable from Web Browser control
I wrote a method Assert():
[System.Diagnostics.Conditional("DEBUG")]
internal static void Assert(bool condition)
{
if (!condition)
{
var message =
"Line:" + (new System.Diagnostics.StackFrame(1)).GetFileLineNumber() + "\r\n" +
"Column:" + (new System.Diagnostics.StackFrame(1)).GetFileColumnNumber() + "\r\n" +
"Where:" + (new System.Diagnostics.StackFrame(1)).GetMethod().Name;
Log("ASSERTION", message);
}
}
Why do I have both line and column being equal to 0, when triggered? It supposed to be the place where Debug.Assert(false) is called.
Regards,
You need to use the StackFrame(int, bool) overload and specify true as the second argument. It looks like just the StackFrame(int) overload doesn't capture source information.
Sample code:
using System.Diagnostics;
...
[Conditional("DEBUG")]
internal static void Assert(bool condition)
{
if (!condition)
{
StackFrame frame = new StackFrame(1, true);
var message = string.Format("Line: {0}\r\nColumn: {1}\r\nWhere:{2}",
frame.GetFileLineNumber(),
frame.GetFileColumnNumber(),
frame.GetMethod().Name);
Log("ASSERTION", message);
}
}
(Looking at your comments by the way, you will need the PDB files. That's where the debug information is stored. It's not at all clear to me whether this will work in a SQLCLR trigger, to be honest. The above works for me in a console app, but that's all I can say...)