I Love ReSharper

Wed 24 January, 2007

This was going to be a post about the different VS 2005 plugins that I use, and some other useful programs for my everyday (most of them Open Source, by the way); but while I was writing it turned itself little by little into a love letter to Resharper. I can only say good things about this productivity plugin.

This are only some of its features:

  • Automatic warning and error checking while you write code; similar to the automatic ortographic revision from MS Office but quite more smart.
  • Better Intellisense. By far, my preferred feature and the one you tend to note first. If you want it to, ReSharper can completely replace VS2005’s Intellisense with its own, faster and more complete. You have the possibility of setting the same font for Intellisense and source code, you can replace Intellisense icons with ReSharper’s set, and add a pop-up with the complete method signature.
  • Code auto-completing, you write the first three characters of virtually anything and ReSharper is already displaying a list of alternatives. If what you’re writing is a method call, pressing TAB inserts the complete method name along with the () and the ;.
  • Templates.
  • Refactoring: you can extract properties from variables, or methods from code: you select a code block, select to extract it as a method, give to it a name and some options, among them are a list of possible parameters that ReSharper automatically detects and voilá!: method created.
  • Advanced search for method use.
  • Type hierarchy view
  • Unit test running with NUnit or csUnit from the IDE.
  • Context actions. In some sections of your code you can see a small floating window with a lightbulb icon. ReSharper wants to help: opening that window with your mouse (or even better, pressing ALT+Enter) ReSharper will show you a list of all the possible actions it can make depending on the code context: check for null values, reorder if sentences, refactor string concatenatios with an StringBuilder, delete unused references, and so on.
  • Automatic detection of unused variables, methods or references. If you declare a variable at the beginning of your code that you later don’t use ReSharper will remark it using a grey color (trust it, it’s quite more noticeable than it sounds), and with that you know that it’s not being used anywhere. ReSharper uses the same color to remark redundant and useless casts or uses.
  • Quick jump to a method or variable definition: using CTRL+Click on the method or variable name you can jump directly into the method or variable definition, if the code is avaliable.
And these are only the tool’s highlights, the tasks I do daily with it. Definitely, it’s the best buy I’ve ever made for a Visual Studio add-in; mostly because I used this last Christmas offer and got it for $99 instead of its normal price tag of $249, which is quite unexpensive if your firm buys it for you: not my case. I bought ReSharper for my exclusive use.

JetBrains development team make Visual Studio 2005 a better yet IDE with this product. The ongoing rumor that they are developing their own version of a .NET IDE, building of IDEA’s true and tested success is, sadly, outdated (look the comments), because it seems the project is abandoned. A real pity.

VirtualBox

Tue 16 January, 2007

When I was beginning in software development, I worked for a firm which (among other things) sells a software application for creating plastic cards. That software was off-the-shelf, shrinkwrapped.

As you can see, the software had to be able to install itself easily and couldn’t use additional components. To make sure of it, every time we compiled a new distributable version we made compatibility tests: on my desk I had four machines, one for development and three older PCs for tests. One of the machines had a Windows 95, another a Windows 98 and the last one a Windows NT; and each of them had only that and the necessary drivers installed. Our software had to work on naked machines, and with each major upgrade we had to format the test machines and reinstall their operating systems, to make sure a perfect installation of our application. The process was so repetitive that I ended unwillingly memorising the serial numbers of each OS.

We managed to speed up the process quite a bit thanks to Norton Ghost: you could make an "naked" OS image file for each machine and after the installation tests of our application, we could reinstall the software from the Ghost image, which quite faster and automatic.

But it was still a pain.

I would have been really happy those days for virtual machine software: either it didn’t exist by that time or I didn’t know about it. For those of you who don’t know what I’m talking about, Para los que no sepáis de lo que hablo, las virtual machines are programs that create a virtual environment between your physical hardware and the operative system installed in that hardware; and in that virtual space you can install another operative system completely different to the one physically installed, in a completely sandboxed environment. You can have a Windows 2003 Server inside a Windows XP, for example.

It’s easier to do than it looks: you assign inside the hard drive of your machine a file that’s going to be the virtual machine hard drive; and you set an amount of RAM to the virtual machine (RAM that’s going to be substracted from your total, physical RAM avaliable, so you need to have plenty for this). You can also mount virtual CD or floppy drives, allow the virtual machine network access, etc. To install an OS you just have to mount an ISO file containing an auto-bootable OS and run the virtual machine. From that point, you simply follow the OS installation instructions as if, in fact, we were installing a new OS in a machine built inside our machine.

I’ve personally used two virtualisation environments: Microsoft Virtual PC, which its version 2004 is free (as in beer) (2007 version is not) and I’ve been quite satisfiied with it: it’s a great virtualisation software, intuitive and easy of use, and the one I use in my workplace.

And the one I’ve just tested at home is InnoTek VirtualBox, Open Source and free (as in freedom). For me at least, the later is more important: I frankly don’t see myself capable of understanding, much less modifying, the source code of one of these things.

Right now I’m writing this entry from a virtual machine made with VirtualBox and running an Ubuntu Dapper Drake. No, I’m not going to the light side: it simply was the ISO file most at hand at the moment to test VirtualBox. In fact, my target for the next days is installing Vista. Long life the dark side! =)

So if you didn’t know virtualisation software, my advice to you is to give it a try: it’s simple to setup, intuitive to use and thanks to this technique we’ll have a safe environment to tinker with harmlessly.

Random Surname Generator

Fri 12 January, 2007

Aswering the challenge raised by Jon Galloway on his must-read blog, here’s is my solution to generate some random and fake surnames. Code has plenty of comments. Feel free, of course, to comment questions, criticism or even better, a cool refactor! =)

class Program
{
    
//An arraylist to contain ALL possible digrams (see below)
    
private static ArrayList AllDigrams = new ArrayList();
    
//An array list to contain ALL possible letters (see below)
    
private static ArrayList AllLetters = new ArrayList();
    
//A random seed for the main random generator
    
private static Random rSeed = new Random()
    
//A multidimensional array specifying the random "bones" of a surname. 
    // "A" is a vowel letter, "B" is a consonant letter
    //"AB is a vowel + consonant digram, and so on…
    //These combinations are made by hand, you could add or delete 
    //to fine-tune the kind of surnames you get
    
private static string[,] SurnameRandomTypes = new string[106]
            {
                {
"A",  "BA""B",  "A",  "BA"""},
                {
"B",  "A",  "BA""",   "",   ""},
                {
"A",  "BA""A",  "B",  "A",  "BB"},
                {
"A",  "B",  "B",  "A",  "BA"""},
                {
"A",  "BB""AB""A",  "",   ""},
                {
"BA""B""B",  "A",  "B",  "A"},
                {
"B",  "AB""A",  "BB""A",  "AB"},
                {
"B",  "AB""B",  "A",  "BB""AB"},
                {
"A",  "BB""A",  "B",  "AB"""},
                {
"AB""AA""B",  "A",  "B",  "A"},
            }
;

    /// <summary>
    /// 
    /// </summary>
    
static void Main()
    {
        
bool exit = false;
        
//We load ALL the possible digrams in memory, 
        //in the AllDigrams arraylist
        
LoadDigrams();
        
//Same for the possible letters, 
        //in the AllLetters arraylist
        
LoadLetters();
        do
        
{
            
for (int 0i <20i++)
            {
                Console.WriteLine(
"Random Surname:    {0}"
                    GenerateRandomSurname())
;
            
}
            Console.WriteLine(
"Press ENTER to generate another batch, X to quit");
            string 
sInput Console.ReadLine();
            if 
(sInput.ToString() == "x" || sInput.ToString() == "X")
                exit 
= true;
        
while (exit == false);
        
Console.ReadLine();
    
}

    /// <summary>
    /// This method generates a random surname and returns it.
    /// </summary>
    /// <returns></returns>
    
static string GenerateRandomSurname()
    {
        
string sRet "";
        
Random r;
        
//New random seed for the randomizer
        
= new Random(rSeed.Next(11000));
        
//and we get a random index for the 
        //SurnameRandomTypes multidimensional array
        
int rndIndex r.Next(09);
        
//Then we read the different "columns" of 
        //that "row" and decide if we need a vowel, 
        //a consonant, a vowel-consonant digram, 
        //or whatever
        
for (int 0i < 6i++)
        {
            
switch(SurnameRandomTypes[rndIndex, i])
            {
                
case "AA":
                    sRet +
getDigram(DigramType.TwoVowels);
                    break;
                case 
"AB":
                    sRet +
getDigram(DigramType.VowelAndConsonant);
                    break;
                case 
"BA":
                    sRet +
getDigram(DigramType.ConsonantAndVowel);
                    break;
                case 
"BB":
                    sRet +
getDigram(DigramType.TwoConsonants);
                    break;
                case 
"A":
                    sRet +
getLetter(LetterType.Vowel);
                    break;
                case 
"B":
                    sRet +
getLetter(LetterType.Consonant);
                    break;
                default
:
                    
break;
            
}
        }
        
//And we return the generated surname 
        //making the first letter upper case
        
return sRet.Substring(0,1).ToUpper() + 
            sRet.Substring(
1);
    
}

    #region Letters and Digrams
    
    
#region Structs and Enums
    
/// <summary>
    /// With this enum we’ll know what 
    /// the digram is made of
    /// </summary>
    
private enum DigramType
    {
        TwoVowels,
        VowelAndConsonant,
        ConsonantAndVowel,
        TwoConsonants
    }

    /// <summary>
    /// With this enum we’ll know is a letter
    /// is a vowel or not
    /// </summary>
    
private enum LetterType
    {
        Vowel,
        Consonant
    }

    /// <summary>
    /// A digram. With the actual digram ("th", for example)
    /// we store the range of frequency it appears on the 
    /// english language and its type
    /// </summary>
    
private struct Digram 
    {
        
public int iniRange;        //The beginning of its range
        
public int endRange;        //The end of its range
        
public string Value;        //Its actual content
        
public DigramType Type;     //Its type
    
}

    /// <summary>
    /// A letter. With the actual letter ("a", for example)
    /// we store the range of frequency it appears on the 
    /// english language and its type
    /// </summary>
    
private struct Letter
    {
        
public int iniRange;        //The beginning of its range
        
public int endRange;        //The end of its range
        
public string Value;        //Its actual content
        
public LetterType Type;     //Its type
    
}
    
#endregion

    /// <summary>
    /// This method returns the value 
    /// of a random digram of the specified type
    /// </summary>
    /// <param name="type">The type of digram the caller needs</param>
    /// <returns>The digram VALUE ("th", for example)</returns>
    
static string getDigram(DigramType type)
    {
        Random r 
= new Random(rSeed.Next(01000));
        
Digram nuDigram = new Digram();
        do
        
{
            
//We get a random number inside the TOTAL 
            //range of ALL the digrams
            
int freq r.Next(05548)//See LoadDigrams()
            
foreach (Digram digram in AllDigrams)
            {
                
//And if the random number is inside the 
                //current digram frequency range, 
                //we’ve got a winner!
                
if (freq >digram.iniRange && 
                    freq <
digram.endRange) 
                        nuDigram 
digram;
            
}
            
//But only if it’s of the type we need
        
while (nuDigram.Type !type);
        return 
nuDigram.Value;
    
}

    /// <summary>
    /// Gets a single letter VALUE of the specified type
    /// </summary>
    /// <param name="type">the type of Letter we need</param>
    /// <returns>the Letter VALUE</returns>
    
static string getLetter(LetterType type) 
    {
        Random r 
= new Random(rSeed.Next(01000));
        
Letter nuLetter = new Letter();
        do
        
{
            
//We get a random number inside the TOTAL 
            //range of ALL the letters
            
int freq r.Next(010025)//See LoadLetters()
            
foreach (Letter letter in AllLetters)
            {
                
//And if the random number 
                //is inside the current digram 
                //frequency range, we’ve got a winner!
                
if (freq >letter.iniRange && 
                    freq <
letter.endRange)
                        nuLetter 
letter;
            
}
            
//But only if it’s of the type we need  
        
while (nuLetter.Type !type);
        return 
nuLetter.Value;
    
}

    /// <summary>
    /// Simple boolean check to see if a given single letter is
    /// a vowel or a consonant
    /// </summary>
    /// <param name="checkLetter">The letter to check</param>
    /// <returns>True if vowel, false if else</returns>
    
static bool IsAVowel(string checkLetter)
    {
        
switch(checkLetter.ToLower())
        {
            
case "a":
            
case "e":
            
case "i":
            
case "o":
            
case "u":
                
return true;
            default
:
                
return false;
        
}
    }

    /// <summary>
    /// Thanks to the University of Bristol Department of Computer Science
    /// (and Google) I’ve managed to get a list of all the letters in the 
    /// english alphabet and their frequency of use in the English language
    /// Since Random deals with integers, I’ve multiplied all these values * 100
    /// The sum of all of them should be 10,000, which when calculating the 
    /// range of random numbers to draw should be 10,000 + the number of possible 
    /// letters; because the first letter range is from 0 to 1231, and the next 
    /// letter range begins en 1232 (prior end range +1)
    /// In the case of single letters this is correct, and the ranges are from 
    /// 0 to 10,025. 
    /// 
    /// But, I don’t know WHY, in the case of digrams the range goes 
    /// from 0 to 5,548. The frequency percentage of all the avaliable digrams,
    /// each of them multiplied by 100 and all of them added is NOT 10,000 as it 
    /// should be. Anyone knows why?
    /// </summary>
    
static void LoadLetters()
    {
        
// http://www.cs.bris.ac.uk/Teaching/Resources/COMS30124/Labs/freq.html
        // Percentage Frequency of Single Letters
        //
        //  E 12.31      L 4.03     B 1.62
        //  T  9.59      D 3.65     G 1.61
        //  A  8.05      C 3.20     V 0.93
        //  O  7.94      U 3.10     K 0.52
        //  N  7.19      P 2.29     Q 0.20
        //  I  7.18      F 2.28     X 0.20
        //  S  6.59      M 2.25     J 0.10
        //  R  6.03      W 2.03     Z 0.09
        //  H  5.14      Y 1.88      

        string[] _letterValue 
        
{
            
"e""l""b""t""d""g""a""c""v",
            
"o""u""k""n""p""q""i""f""x",
            
"s""m""j""r""w""z""h""y"
        
};
        int
[] _letterFreq 
        
{
            
123140316295936516180532093
            
794310527192292071822820,
            
659225106032039514188
        
};
        int 
lastEndRange 0;
        
//Examining those hard-coded arrays we fill 
        //the AllLetters collection of letter objects, 
        //assigning each one its range, value and LetterType
        
for (int 0i < _letterFreq.Lengthi++)
        {
            Letter nuLetter 
= new Letter();
            
nuLetter.iniRange lastEndRange;
            
nuLetter.endRange lastEndRange + _letterFreq[i];
            
lastEndRange nuLetter.endRange + 1;
            
nuLetter.Value _letterValue[i];
            
nuLetter.Type IsAVowel(_letterValue[i]) ? 
                LetterType.Vowel : LetterType.Consonant
;
            
AllLetters.Add(nuLetter);
        
}
    }
     
    
/// <summary>
    /// See LoadLetters comment
    /// </summary>
    
static void LoadDigrams()
    {
        
// http://www.cs.bris.ac.uk/Teaching/Resources/COMS30124/Labs/freq.html
        //
        //  TH  3.15   TO  1.11   SA  0.75   MA  0.56 
        //  HE  2.51   NT  1.10   HI  0.72   TA  0.56
        //  AN  1.72   ED  1.07   LE  0.72   CE  0.55
        //  IN  1.69   IS  1.06   SO  0.71   IC  0.55
        //  ER  1.54   AR  1.01   AS  0.67   LL  0.55
        //  RE  1.48   OU  0.96   NO  0.65   NA  0.54
        //  ES  1.45   TE  0.94   NE  0.64   RO  0.54
        //  ON  1.45   OF  0.94   EC  0.64   OT  0.53
        //  EA  1.31   IT  0.88   IO  0.63   TT  0.53
        //  TI  1.28   HA  0.84   RT  0.63   VE  0.53
        //  AT  1.24   SE  0.84   CO  0.59   NS  0.51
        //  ST  1.21   ET  0.80   BE  0.58   UR  0.49
        //  EN  1.20   AL  0.77   DI  0.57   ME  0.48
        //  ND  1.18   RI  0.77   LI  0.57   WH  0.48
        //  OR  1.13   NG  0.75   RA  0.57   LY  0.47 

        string[] _digramValue 
        
{   
            
"th""to""sa""ma""he""nt""hi""ta",
            
"an""ed""le""ce""in""is""so""ic",
            
"er""ar""as""ll""re""ou""no""na",
            
"es""te""ne""ro""on""of""ec""ot",
            
"ea""it""io""tt""ti""ha""rt""ve",
            
"at""se""co""ns""st""et""be""ur",
            
"en""al""di""me""nd""ri""li""wh",
            
"or""ng""ra""ly"
        
};
        int
[] _digramFreq 
        
{
            
31511175562511107256,
            
17210772551691067155
            
1541016755148966554
            
145946454145946453
            
131886353128846353
            
124845951121805849
            
120775748118775748
            
113755747
        
};
        int 
lastEndRange 0;
        
//Examining those hard-coded arrays 
        //we fill the AllDigrams collection
        //of Digram objects, assigning each 
        //one its range, value and DigramType
        
for (int 0i < _digramFreq.Lengthi++)
        {
            Digram nuDigram 
= new Digram();
            
nuDigram.iniRange lastEndRange;
            
nuDigram.endRange lastEndRange + 
                _digramFreq[i]
;
            
lastEndRange nuDigram.endRange + 1;
            
nuDigram.Value _digramValue[i];
            if 
(IsAVowel(_digramValue[i].Substring(01)) && 
                IsAVowel(_digramValue[i].Substring(
11)))
                    nuDigram.Type 
DigramType.TwoVowels;
            else if 
(!IsAVowel(_digramValue[i].Substring(01)) && 
                IsAVowel(_digramValue[i].Substring(
11)))
                    nuDigram.Type 
DigramType.ConsonantAndVowel;
            else if 
(IsAVowel(_digramValue[i].Substring(01)) && 
                !IsAVowel(_digramValue[i].Substring(
11)))
                    nuDigram.Type 
DigramType.VowelAndConsonant;
            else 
nuDigram.Type DigramType.TwoConsonants;
            
AllDigrams.Add(nuDigram);
        
}
    }
#endregion
}


Colorized by: CarlosAg.CodeColorizer

And here’s an example of the surnames it makes:

  • Andaso
  • Estecon
  • Ovloso
  • Eleasawh
  • Athene
  • Tertore
  • Setolyiis
  • Face
  • Lirsuco
  • Rofsender

kick it on DotNetKicks.com

Compressing and uncompressing ZIP files

Thu 4 January, 2007

For compressing and uncompresing files in ZIP format we have a very valuable tool at our disposal: the SharpZipLib library, made by IC#Code, the same developer group that brought us the awesome Open Source IDE for C# SharpDevelop, which I’ve mentioned before.

First thing we’ve got to do, then, is to download said library to our system. Once donde that, we must include it as a reference on the project we’re using it; and if we believe we’re going to use it somewhat frequently, the comfy option is to install it to the GAC. To do so, let’s open the Visual Studio 2005 Command prompt, located under Tools in the Visual Studio 2005 start menu group. When clicking on it, it opens a new console window and inside it we should go to the folder where we’ve downloaded ICSharpCode.SharpZipLib.dll (for easiness, I have it on a folder of its own at C:\Program Files) and once there we enter the following command:

gacutil /i ICSharpCode.SharpZipLib.dll

Thus we add the library to .NET’s global assembly cache.

So, either if we add the library to the GAC or not, we have to reference it in our project. For it, you know, once our project is created (be it a Console, Windows Forms or Web Forms) whe choose the Project, Add Reference menu item, and we choose the library on the dialog window that opens: on the .NET tab if the library is included in the GAC or we can search it with the tab Browse. Once the reference is included, we add the line


using ICSharpCode.SharpZipLib.Zip;

in our code and we are set to go.
One last disclaimer before gettint to it: to simplify this article I’ve decided for the code examples included to work in the Console and I’ve taken for granted some things like paths and file names. If you want this code to work don’t simply copy and paste it, you’ve got to adapt it to your system and your files.

Listing File Content

Let’s see first how can we list the contents of a ZIP file. for it, we simply declare an object called zip from the class ZipFile. See how SharpZipLib makes a ZipFile composed of ZipEntry objects, not of files or lines or any other thing. The ZipEntry class is going to be any of the entities that can be contained on a ZIP file, files or directories. Once knowing this, we can work our way through the entries on the ZIP file with a foreach loop and we simply show the name of the entry on the Console:

private static void ListZipContent(string sFile) 
{
    ZipFile zip 
= new ZipFile(File.OpenRead(sFile));
    foreach
(ZipEntry entry in zip) 
    {
        Console.WriteLine(entry.Name)
;
    
}

Colorized by: CarlosAg.CodeColorizer

Uncompressing Files

The plot thickens. To uncompress a ZIP file we open it as a ZipInputStream object, we navigate through its entries with the GetNextEntry() method and we save the binary content of each entry on a byte array. Said array will feed a StreamWriter object, which we’ll use to create the new file, which will be an exact uncompressed copy of the binary data stored on the ZIP file’s entry.

private static void UncompressZip(string sFile)
{
    ZipInputStream zipIn 
= new ZipInputStream(File.OpenRead(sFile));
    
ZipEntry entry;
    while 
((entry zipIn.GetNextEntry()) != null)
    {
        FileStream streamWriter 
File.Create(@"C:\Temp\" + entry.Name);
        long 
size entry.Size;
        byte
[] data = new byte[size];
        while 
(true)
        {
            size 
zipIn.Read(data, 0, data.Length);
            if 
(size > 0) streamWriter.Write(data, 0, (int) size);
            else break;
        
}
        streamWriter.Close()
;
    
}
    Console.WriteLine(
"Done!!");

Colorized by: CarlosAg.CodeColorizer

Compressing Files

For compressing files wer’ll use an object similar to the previous one but of the opposite purpose, ZipOutputStream. Wanting to keep this example simple, I’ve supposed we want to compress every existing file on the path passed as parameter sPath. Thus we’ll read all the files sitting on that folder, we’ll create a new ZipEntry object, get some information about the file with FileInfo (we could also use some CRC checking, but I wanted to keep this simple) to assign that information to the new entry, and add the entry to the ZipOutputStream object with its PutNextEntry method. Pay attention, this would create an empty entry: to really enter the data contained in the file we need to read its binary data with a FileStream as in the previous example and then call the method Write from ZipOutputStream to save those binary data on the most recent entry of the output Zip stream. Once read and included all the files, we finish and close the ZipOutputStream object.

private static void CompressZip(string sPath) 
{
    ZipOutputStream zipOut  
= new ZipOutputStream(File.Create(@"C:\Temp\test.zip"));
    foreach
(string fName in Directory.GetFiles(sPath))
    {
        FileInfo fi  
= new FileInfo(fName);
        
ZipEntry entry = new ZipEntry(fi.Name);
        
FileStream sReader File.OpenRead(fName);
        byte
[] buff = new byte[Convert.ToInt32(sReader.Length)];
        
sReader.Read(buff, 0, (int) sReader.Length);
        
entry.DateTime fi.LastWriteTime;
        
entry.Size sReader.Length;
        
sReader.Close();
        
zipOut.PutNextEntry(entry);
        
zipOut.Write(buff, 0, buff.Length);
    
}
    zipOut.Finish()
;
    
zipOut.Close();
    
Console.WriteLine("Done!!");

Colorized by: CarlosAg.CodeColorizer

And this ends this little introduction to SharpZipLib. IC#Code keeps an superb forum on which you can find some more code examples and a lot of answered questions.

Update 5/01/2007.- Playing a little bit with the library and VS 2005’s Object Browser, I stumble upon a class called FastZip. Hmmmmm. Yeah, it’s what it seems: a wrapper class to ease the process of compressing and uncompressing files with SharpZipLib. For example, to uncompress a file it would be as simple as this:

ExtractZip method’s three parameters are strings; and they stand for the name of the ZIP file to uncompress, the destination path for the uncompressed files and a mask for the type of files we want to extract. Warning: you can’t use an DOS mask such as "*.txt", you must use a regular expression. If we want to uncompress every file existing on the path, we should simply leave this last argument as null or empty string "".

private static void FastZipUncompress(string sFile)
{
    FastZip fZip 
= new FastZip();
    
fZip.ExtractZip(sFile, @"C:\Temp""");

Colorized by: CarlosAg.CodeColorizer

And to compress:

private static void FastZipCompress()
{
    FastZip fZip 
= new FastZip();
    
fZip.CreateZip(@"C:\Temp\walls.zip"@"C:\Wallpapers"false".jpg$");
    
Console.WriteLine("Done!!");

Colorized by: CarlosAg.CodeColorizer

In this example the method CreateZip accepts four arguments: the name and path of the ZIP file to be created, the folder where the files we want to compress are, a boolean parameter for stating if we want to make a recursive compression (to include possible subfolders), and last an string parameter specifying the mask we want to use for choosing the files we want to compress, in this example every JPG file. The same rules in using masks applies.

kick it on DotNetKicks.com