How to generate a File - c#

I want to generate a .txt file when user clicks a button. The issue is I am not sure how to do it? I would like to have it so it uses this format:
00000012 <-- at the very start of the text file
2011 11 29 <-- Year 2011, Month 11, Day 29 (the year month and day based on my PC)
0010 or 0054 <-- this is a random number randomly one of these 2 numbers...
123456 <-- Time, Hour 12, Minutes 34, Seconds 56.
so when I open the text file it should be something like this:
00000012201111290054123456
I am new to C#. I was able to accomplish this on visual basic with this:
Public Class Form1
'declare new random object
Dim r As New Random
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'declare string + set initial value
Dim fileString As String = "00000012"
'add formatted date
fileString &= Now.ToString("yyyyMMdd")
'add random 0010 or 0054
fileString &= New String() {"0010", "0054"}(r.Next(0, 2))
'add random 6 digit number
fileString &= r.Next(0, 1000000).ToString("000000")
'write file
IO.File.WriteAllText("C:\Program Files\Code\maincode\maincode.txt", fileString)
End Sub
End Class
I have decided to change the last random 6 generation to the time instead. How to do it in C#?

The easiest way to solve your problem would be to use String.Format, which works pretty much the same way in VB.NET as it does in C#.
Before getting to that, though, it's worth addressing some issues with your VB.NET code; fixing these will make your C# code a lot better as well:
Your Random instance, r, is declared at the form (module) scope, but you only use it inside your Form1_Load procedure: it's a good idea to always keep your variable scope as limited as possible, in other words keep your declarations "as close to" the usage as you can;
While on the subject of System.Random: do keep in mind that this actually creates a quite predictable (time-based) series of numbers, so it shouldn't be used for anything security-related (that's what System.Security.Cryptography.RNGCryptoServiceProvider is for). Your usage of Random isn't very strong anyway, and I'm not sure of your actual use case, but this is always something to keep in mind;
You use comments that almost literally describe what your code does, which is rather pointless: only add comments to add additional insight into why your code does the things it does, not how;
Strings in .NET are immutable, which means that each time you use the & operator, you create a new string, leaving the old one to linger around until garbage collection kicks in. That gets really expensive after a while: instead, learn about Text.StringBuilder and use it whenever needed;
You create a file under C:\Program Files. Apart from the fact that that directory may have a different name on different versions of Windows (not to mention Linux, in case you're running on Mono), users don't have permission to write there on any non-legacy (i.e. post-XP) versions of Windows. So, if you do this, your program will crash when distributed: if you learn to use the proper file locations early on, that will save you lots of trouble.
Anyway, on to your question: whenever you want to create a string with lots of parameters and/or formatting, String.Format and its custom format strings comes in extremely handy. For example, your original VB.NET code can be rewritten as follows:
Sub Form1_Load(s As Object, e As EventArgs)
Using sw As New IO.StreamWriter(IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"maincode.txt"))
sw.WriteLine("00000012{0:yyyyMMdd}{1}{0:HHmmss}", Now,
If((New Random).Next(0, 2) = 0, "0010", "0054"))
End Using
End Sub
When executed, this should create C:\Users\YourUserName\AppData\Roaming\maincode.txt (or somewhere else if you're on XP or certain localized versions of Windows: check the value of Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) in the debugger to find out).
Translating this to C# is trivial: if you use one of the many VB.NET to C# translators available online, it should give you something like this:
public void Form1_Load(object s, EventArgs e)
{
using (System.IO.StreamWriter sw = new System.IO.StreamWriter(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "maincode.txt"))) {
sw.WriteLine("00000012{0:yyyyMMdd}{1}{0:HHmmss}", DateAndTime.Now, (new Random()).Next(0, 2) == 0 ? "0010" : "0054");
}
}
Basically, it added some semicolons and curly braces for you, included fully-qualified namespaces and rewrote the ternary condition: the .NET Framework bits are identical.
Hope this helps, and good luck with your programming efforts!

Start with creating Text files with C# then go through this tabel or this one and rewrite your code in C# to build the information as described in your question.

Related

Read/Write array to a file

I need guidance, someone to point me in the right direction. As the tittle says, I need to save information to a file: Date, string, integer and an array of integers. And I also need to be able to access that information later, when an user wants to review it.
Optional: File is plain text and I can directly check it and it is understandable.
Bonus points if chosen method can be "easily" converted to working with a database in the future instead of individual files.
I'm pretty new to C# and what I've found so far is that I should turn the array into a string with separators.
So, what'd you guys suggest?
// JSON.Net
string json = JsonConvert.SerializeObject(objOrArray);
File.WriteAllText(path, json);
// (note: can also use File.Create etc if don't need the string in memory)
or...
using(var file = File.Create(path)) { // protobuf-net
Serializer.Serialize(file, objOrArray);
}
The first is readable; the second will be smaller. Both will cope fine with "Date, string, integer and an array of integers", or an array of such objects. Protobuf-net would require adding some attributes to help it, but really simple.
As for working with a database as columns... the array of integers is the glitch there, because most databases don't support "array of integers" as a column type. I'd say "separation of concerns" - have a separate model for DB persistence. If you are using the database purely to store documents, then: pretty much every DB will support CLOB and BLOB data, so either is usable. Many databases now have inbuilt JSON support (helper methods, etc), which might make JSON as a CLOB more tempting.
I would probably serialize this to json and save it somewhere. Json.Net is a very popular way.
The advantage of this is also creating a class that can be later used to work with an Object-Relational Mapper.
var userInfo = new UserInfoModel();
// write the data (overwrites)
using (var stream = new StreamWriter(#"path/to/your/file.json", append: false))
{
stream.Write(JsonConvert.SerializeObject(userInfo));
}
//read the data
using (var stream = new StreamReader(#"path/to/your/file.json"))
{
userInfo = JsonConvert.DeserializeObject<UserInfoModel>(stream.ReadToEnd());
}
public class UserInfoModel
{
public DateTime Date { get; set; }
// etc.
}
for the Plaintext File you're right.
Use 1 Line for each Entry:
Date
string
Integer
Array of Integer
If you read the File in your code you can easily seperate them by reading line to line.
Make a string with a specific Seperator out of the Array:
[1,2,3] -> "1,2,3"
When you read the line you can Split the String by "," and gets a Array of Strings. Parse each Entry to int into an Array of Int with the same length.
How to read and write the File get a look at Easiest way to read from and write to files
If you really wants the switch to a database at a point, try a JSON Format for your File. It is easy to handle and there are some good Plugins to work with.
Mfg
Henne
The way I got started with C# is via the game Space Engineers from the Steam Platform, the Mods need to save a file Locally (%AppData%\Local\Temp\SpaceEngineers\ or %AppData%\Roaming\SpaceEngineers\Storage\) for various settings, and their logging is similar to what #H. Sandberg mentioned (line by line, perhaps a separator to parse with later), the upside to this is that it's easy to retrieve, easy to append, easy to overwrite, and I'm pretty sure it's even possible to retrieve File Size, which when combined with File Deletion and File Creation can prevent runaway File Sizes as this allows you to set an Upper Limit to check against, allowing you to run it on a Server with minimal impact (probably best to include a minimum Date filter {make sure X is at least Y days old before deleting it for being over Z Bytes} to prevent Debugging Data Loss {"Why was it over that limit?"})
As far as the actual Code behind the idea, I'm approximately at the same Skill Level as the OP, which is to say; Rookie, but I would advise looking at the Coding in the Space Engineers Mods for some Samples (plus it's not half bad for a Beta Game), as they are almost all written in C#. Also, the Programmable Blocks compile in C# as well, so you'll be able to use that to both assist in learning C# and reinforce and utilize what you already know (although certain C# commands aren't allowed for security reasons, utilizing the Mod API you'll have more flexibility to do things such as Creating/Maintaining Log Files, Retrieving/Modifying Object Properties, etc.), You are even capable of printing Text to various in Game Text Monitors.
I apologise if my Syntax needs some work, and I'm sorry I am not currently capable of just whipping up some Code to solve your issue, but I do know
using System;
Console.WriteLine("Hello World");
so at least it's not a total loss, but my example Code likely won't compile, since it's likely missing things like: an Output Location, perhaps an API reference or two, and probably a few other settings. Like I said, I'm New, but that is a valid C# Command, I know I got that part correct.
Edit: here's a better attempt:
using System;
class Test
{
static void Main()
{
string a = "Hello Hal, ";
string b = "Please open the Airlock Doors.";
string c = "I'm sorry Dave, "
string d = "I'm afraid I can't do that."
Console.WriteLine(a + b);
Console.WriteLine(c + d);
Console.Read();
}
}
This:
"Hello Hal, Please open the Airlock Doors."
"I'm sorry Dave, I'm afraid I can't do that."
Should be the result. (the "Quotation Marks" shouldn't appear in the readout {the last Code Block}, that's simply to improve readability)

getting unstructured CSV-madness into order: does this make sense?

I have logfiles in the file-format of csv.
The vendor of the app it comes from wants to stick with it.
MY goal is to build a logreader that does some highlighting and filtering.
The CSV looks ugly as hell, because it is not at all tidy, even if you put some work into it in Excel. A sample of that structure is attached.
Now, i've been pondering about the solution of this a while and came up with a kinda complex solution. But i don't like that solution. It feels too "special"
But Let's look at what we have:
- The columns and their order are defined by a customizable view in the app.
So sometimes e.g. the date-column is first, could also be last, could be missing even. The output is not in the correct order. Sometimes the DAte-coulmn contains the Details-Text. Sometimes a MAC. It's madness.
Edit: Here is how it can look when it's raw data:
http://s000.tinyupload.com/?file_id=79649596476923658435
So what i came up with is this:
- read line1 of your csv, then you know the columns of that file.
- read a config-file that defines "detection-rules" for Columns with set names. So for instance you have a filter for a Colunm for Mac-addresses. One for IP-addresses and vice versa. The Columnns themself are predefined so this is at least possible.
so you read the CSV-line2 and spilt that string to an array, loop through and check for each string if that string matches one of your filters.
Then - if you have a match - you loop through the array you created with the Column-titles (you read line1 of the CSV)
The looping-instance increments a number up.
Then you have another array with the same size as the array containing the titles. You put the found value in there (the incremented number represents the index you use in the new array)
This all probably sounds hard to understand so i wrote a bit of code to illustrate how i meant it.
Am i missing something or is this the way to go with what i have ?
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim Line1csv() As String = {"Date", "MAC"}
Dim csvdata As String() = System.IO.File.ReadAllLines("tst.csv")
For Each csvline In csvdata
'lets split the line into pot. cell-values
Dim Csvlinestr() As String = csvline.Split(",")
'now lets loop through what we created and find out what it is
For Each csvfielstr In Csvlinestr
'ok lets ask what this field is
Dim Csvlinetype As String = AskmeWhatitis(csvfielstr)
'good lets say it told us that the result is title2
'so where do we put it ? lets loop through the line1csv to find out the indx
Dim found As Boolean = False
Dim Indx As Integer = 0
For Each l1csvstr In Line1csv
If l1csvstr = Csvlinetype Then
found = True
End If
Indx += 1
Next
If found = True Then
' here we'd put it into our "sorted array that has the same size as the line1csv-array()
' the index used is sthe indx-variable we used above
End If
Next
Next
End Sub
Public Function AskmeWhatitis(ByVal instr As String) As String
' this sub has a config-file that tells it what to search how and what the match will mean: does it mean its a MAC or date or whaterver
Return "truth"
End Function
End Class

Named numbers as variables [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 8 years ago.
Improve this question
I've seen this a couple of times recently in high profile code, where constant values are defined as variables, named after the value, then used only once. I wondered why it gets done?
E.g. Linux Source (resize.c)
unsigned five = 5;
unsigned seven = 7;
E.g. C#.NET Source (Quaternion.cs)
double zero = 0;
double one = 1;
Naming numbers is terrible practice, one day something will need to change, and you'll end up with unsigned five = 7.
If it has some meaning, give it a meaningful name. The 'magic number' five is no improvement over the magic number 5, it's worse because it might not actually equal 5.
This kind of thing generally arises from some cargo-cult style programming style guidelines where someone heard that "magic numbers are bad" and forbade their use without fully understanding why.
Well named variables
Giving proper names to variables can dramatically clarify code, such as
constant int MAXIMUM_PRESSURE_VALUE=2;
This gives two key advantages:
The value MAXIMUM_PRESSURE_VALUE may be used in many different places, if for whatever reason that value changes you need to change it in only one place.
Where used it immediately shows what the function is doing, for example the following code obviously checks if the pressure is dangerously high:
if (pressure>MAXIMUM_PRESSURE_VALUE){
//without me telling you you can guess there'll be some safety protection in here
}
Poorly named variables
However, everything has a counter argument and what you have shown looks very like a good idea taken so far that it makes no sense. Defining TWO as 2 doesn't add any value
constant int TWO=2;
The value TWO may be used in many different places, perhaps to double things, perhaps to access an index. If in the future you need to change the index you cannot just change to int TWO=3; because that would affect all the other (completely unrelated) ways you've used TWO, now you'd be tripling instead of doubling etc
Where used it gives you no more information than if you just used "2". Compare the following two pieces of code:
if (pressure>2){
//2 might be good, I have no idea what happens here
}
or
if (pressure>TWO){
//TWO means 2, 2 might be good, I still have no idea what happens here
}
Worse still (as seems to be the case here) TWO may not equal 2, if so this is a form of obfuscation where the intention is to make the code less clear: obviously it achieves that.
The usual reason for this is a coding standard which forbids magic numbers but doesn't count TWO as a magic number; which of course it is! 99% of the time you want to use a meaningful variable name but in that 1% of the time using TWO instead of 2 gains you nothing (Sorry, I mean ZERO).
this code is inspired by Java but is intended to be language agnostic
Short version:
A constant five that just holds the number five is pretty useless. Don't go around making these for no reason (sometimes you have to because of syntax or typing rules, though).
The named variables in Quaternion.cs aren't strictly necessary, but you can make the case for the code being significantly more readable with them than without.
The named variables in ext4/resize.c aren't constants at all. They're tersely-named counters. Their names obscure their function a bit, but this code actually does correctly follow the project's specialized coding standards.
What's going on with Quaternion.cs?
This one's pretty easy.
Right after this:
double zero = 0;
double one = 1;
The code does this:
return zero.GetHashCode() ^ one.GetHashCode();
Without the local variables, what does the alternative look like?
return 0.0.GetHashCode() ^ 1.0.GetHashCode(); // doubles, not ints!
What a mess! Readability is definitely on the side of creating the locals here. Moreover, I think explicitly naming the variables indicates "We've thought about this carefully" much more clearly than just writing a single confusing return statement would.
What's going on with resize.c?
In the case of ext4/resize.c, these numbers aren't actually constants at all. If you follow the code, you'll see that they're counters and their values actually change over multiple iterations of a while loop.
Note how they're initialized:
unsigned three = 1;
unsigned five = 5;
unsigned seven = 7;
Three equals one, huh? What's that about?
See, what actually happens is that update_backups passes these variables by reference to the function ext4_list_backups:
/*
* Iterate through the groups which hold BACKUP superblock/GDT copies in an
* ext4 filesystem. The counters should be initialized to 1, 5, and 7 before
* calling this for the first time. In a sparse filesystem it will be the
* sequence of powers of 3, 5, and 7: 1, 3, 5, 7, 9, 25, 27, 49, 81, ...
* For a non-sparse filesystem it will be every group: 1, 2, 3, 4, ...
*/
static unsigned ext4_list_backups(struct super_block *sb, unsigned *three,
unsigned *five, unsigned *seven)
They're counters that are preserved over the course of multiple calls. If you look at the function body, you'll see that it's juggling the counters to find the next power of 3, 5, or 7, creating the sequence you see in the comment: 1, 3, 5, 7, 9, 25, 27, &c.
Now, for the weirdest part: the variable three is initialized to 1 because 30 = 1. The power 0 is a special case, though, because it's the only time 3x = 5x = 7x. Try your hand at rewriting ext4_list_backups to work with all three counters initialized to 1 (30, 50, 70) and you'll see how much more cumbersome the code becomes. Sometimes it's easier to just tell the caller to do something funky (initialize the list to 1, 5, 7) in the comments.
So, is five = 5 good coding style?
Is "five" a good name for the thing that the variable five represents in resize.c? In my opinion, it's not a style you should emulate in just any random project you take on. The simple name five doesn't communicate much about the purpose of the variable. If you're working on a web application or rapidly prototyping a video chat client or something and decide to name a variable five, you're probably going to create headaches and annoyance for anyone else who needs to maintain and modify your code.
However, this is one example where generalities about programming don't paint the full picture. Take a look at the kernel's coding style document, particularly the chapter on naming.
GLOBAL variables (to be used only if you really need them) need to
have descriptive names, as do global functions. If you have a function
that counts the number of active users, you should call that
"count_active_users()" or similar, you should not call it "cntusr()".
...
LOCAL variable names should be short, and to the point. If you have
some random integer loop counter, it should probably be called "i".
Calling it "loop_counter" is non-productive, if there is no chance of it
being mis-understood. Similarly, "tmp" can be just about any type of
variable that is used to hold a temporary value.
If you are afraid to mix up your local variable names, you have another
problem, which is called the function-growth-hormone-imbalance syndrome.
See chapter 6 (Functions).
Part of this is C-style coding tradition. Part of it is purposeful social engineering. A lot of kernel code is sensitive stuff, and it's been revised and tested many times. Since Linux is a big open-source project, it's not really hurting for contributions — in most ways, the bigger challenge is checking those contributions for quality.
Calling that variable five instead of something like nextPowerOfFive is a way to discourage contributors from meddling in code they don't understand. It's an attempt to force you to really read the code you're modifying in detail, line by line, before you try to make any changes.
Did the kernel maintainers make the right decision? I can't say. But it's clearly a purposeful move.
My organisation have certain programming guidelines, one of which is the use of magic numbers...
eg:
if (input == 3) //3 what? Elephants?....3 really is the magic number here...
This would be changed to:
#define INPUT_1_VOLTAGE_THRESHOLD 3u
if (input == INPUT_1_VOLTAGE_THRESHOLD) //Not elephants :(
We also have a source file with -200,000 -> 200,000 #defined in the format:
#define MINUS_TWO_ZERO_ZERO_ZERO_ZERO_ZERO -200000
which can be used in place of magic numbers, for example when referencing a specific index of an array.
I imagine this has been done for "Readability".
The numbers 0, 1, ... are integers. Here, the 'named variables' give the integer a different type. It might be more reasonable to specify these constant (const unsigned five = 5;)
I've used something akin to that a couple times to write values to files:
const int32_t zero = 0 ;
fwrite( &zero, sizeof(zero), 1, myfile );
fwrite accepts a const pointer, but if some function needs a non const pointer, you'll end up using a non const variable.
P.S.: That always keeps me wondering what may be the sizeof zero .
How do you come to a conslusion that it is used only once? It is public, it could be used any number of times from any assembly.
public static readonly Quaternion Zero = new Quaternion();
public static readonly Quaternion One = new Quaternion(1.0f, 1.0f, 1.0f, 1.0f);
Same thing applies to .Net framework decimal class. which also exposes public constants like this.
public const decimal One = 1m;
public const decimal Zero = 0m;
Numbers are often given a name when these numbers have special meaning.
For example in the Quaternion case the identity quaternion and unit length quaternion have special meaning and are frequently used in a special context. Namely Quaternion with (0,0,0,1) is an identity quaternion so it's a common practice to define them instead of using magic numbers.
For example
// define as static
static Quaternion Identity = new Quaternion(0,0,0,1);
Quaternion Q1 = Quaternion.Identity;
//or
if ( Q1.Length == Unit ) // not considering floating point error
One of my first programming jobs was on a PDP 11 using Basic. The Basic interpreter allocated memory to every number required, so every time the program mentioned 0, a byte or two would be used to store the number 0. Of course back in those days memory was a lot more limited than today and so it was important to conserve.
Every program in that work place started with:
10 U0%=0
20 U1%=1
That is, for those who have forgotten their Basic:
Line number 10: create an integer variable called U0 and assign it the number 0
Line number 20: create an integer variable called U1 and assign it the number 1
These variables, by local convention, never held any other value, so they were effectively constants. They allowed 0 and 1 to be used throughout the program without wasting any memory.
Aaaaah, the good old days!
some times it's more readable to write:
double pi=3.14; //Constant or even not constant
...
CircleArea=pi*r*r;
instead of:
CircleArea=3.14*r*r;
and may be you would use pi more again (you are not sure but you think it's possible later or in other classes if they are public)
and then if you want to change pi=3.14 into pi=3.141596 it's easier.
and some other like e=2.71, Avogadro and etc.

C# / VisualStudio: Sorting attributes for consistency - any hints?

I have a bit of a silly issue:
I have a large number of unit tests which all have method attributes like this:
[TestMethod]
[Owner("me")]
[Description("It tests something.")]
[TestProperty(TC.Name, "Some Test")]
[TestProperty(TC.Requirement, "req203")]
[TestProperty(TC.Reviewer, "someguy")]
[TestProperty(TC.Environment, "MSTest")]
[TestProperty(TC.CreationDate, "24.01.2012")]
[TestProperty(TC.InternalTcId, "{9221A494-2B31-479D-ADE6-D4773C2A9B08}")]
public void TestSomething()
{ ... }
(If you're wondering: these attributes are used for automated testing and requirement coverage stuff.. )
Now, unfortunately these attributes are in a different order at most test methods - which makes it a bit messy to review and such. So I'm looking for a way to order them..
Would you know any other way than rearranging them manually?
(I thought about writing some VS plugin or so) - I'm just wondering whether I'm really the first person with that wish.
Open up the Macro Explorer - and paste this code into a module (It's straight from my own little collection of macros):
Sub Sort()
Dim selection As EnvDTE.TextSelection = DTE.ActiveDocument.Selection
If selection Is Nothing Or String.IsNullOrWhiteSpace(selection.Text) Then
Exit Sub
End If
Dim lines As String() = selection.Text.Split(vbCrLf.ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
If lines.Length <= 1 Then Exit Sub
lines = lines.OrderBy(Function(s As String) s, StringComparer.CurrentCulture).ToArray()
DTE.UndoContext.Open("Sort Lines")
selection.Insert(String.Join(vbCrLf, lines))
selection.SmartFormat()
DTE.UndoContext.Close()
DTE.StatusBar.Text = "Sort Lines complete"
selection.SmartFormat()
End Sub
(just edited it as the try/end try wasn't really right - so I've taken it out)
Now you can bind a shortcut to this macro in VS - it uses a Linq OrderBy using the current culture's string comparer to sort the lines of the currently selected block of text. It should therefore group the attributes together accordingly.
If you need something that context-sensitive (i.e. the same attribute being called with different numbers of parameters) - then you'll need to do considerably more work.
You are the first person with that wish :)
I would arrange them manually, but also, if you are looking for a more solid solution, then I would implement a property in the TestPropertyAttribute class int Index { get; set; } and set the order in which I want them processed. In that case, you can control which how attributes are read in the reflection code that reads them.
This is how NHibernate does it.
[TestProperty(TC.Name, "Some Test", 0)]
[TestProperty(TC.Requirement, "req203", 1)]

In ASP, C#, and VB.Net how to retrieve current line number

Does ASP, C#, VB.NET have a way to retrieve what line its on in code as its processing commands?
Example
1 <%
2 response.write("Your on line " & retreiveCurrentLineNumber)
3 %>
Output: Your on line 2
You can do this:
var line = new StackFrame(0, true).GetFileLineNumber();
Note there are several caveats to this.
You will need to make sure the source file and PDB are reachable.
This will get you the current line of the method you are in, not exactly where you are.
The Jit may perform optimizations that result in incorrect information, such as a method being inlined.
For VB.NET it's the same thing:
Dim line As Integer = New StackFrame(0, True).GetFileLineNumber()
As far as Classic ASP goes - I don't believe this is possible.
While vcsjones answer may be exactly what you're looking for, for the purposes of debugging/troubleshooting VB.NET you may want to take a look at the Erl property of the Err object. It returns an integer indicating the line number of the last executed statement - and by line number, that means a numeric label, not the physical line number of the source file.
Peppering one's code with line numbers at critical points is helpful at troubleshooting the unexpected exceptions, and one doesn't need the source file and PDB to make Erl work.

Categories