A class is a language construct that binds together member functions and data, providing access to users only to
public member functions, not to its data. This encapsulation enables the class to make strong guarantees about
validity of its data. Instances of all classes are reference types and live on the managed heap.
///////////////////////////////////////////////////////////////////////
/// Navigate.cs - Navigates a Directory Subtree, displaying files ///
/// ver 1.3 and some of their properties ///
/// ///
/// Language: Visual C# ///
/// Platform: Dell Dimension 8100, Windows Pro 2000, SP2 ///
/// Application: CSE681 Example ///
/// Author: Jim Fawcett, CST 2-187, Syracuse Univ. ///
/// (315) 443-3948, jfawcett@twcny.rr.com ///
///////////////////////////////////////////////////////////////////////
/*
* Module Operations:
* ==================
* Recursively displays the contents of a directory tree
* rooted at a specified path, with specified file pattern.
*
* Maintenance History:
* ====================
* elided
*/
using System;
using System.IO;
namespace Navigate
{
public class Navigate
{
private int fileCount_ = 0;
public int fileCount {
get { return fileCount_; } // can add additional logic without
set { fileCount_ = value; } // changing user interface
}
public int dirCount { get; set; } = 0; // equivalent to the above
public void go(string path, string pattern)
{
path = Path.GetFullPath(path);
// get all files in this directory and display them
string [] files = Directory.GetFiles(path, pattern);
if (files.Length > 0) // show only dirs with files
{
Console.Write("\n\n {0}", path);
}
foreach (string file in files)
{
++fileCount;
string name = Path.GetFileName(file);
FileInfo fi = new FileInfo(file);
DateTime dt = File.GetLastWriteTime(file);
Console.Write("\n {0,-50} {1,8} bytes {2}",name,fi.Length,dt);
}
// for each subdirectory in this directory display its files
// recursively
string[] dirs = Directory.GetDirectories(path);
foreach (string dir in dirs)
{
++dirCount;
go(dir, pattern);
}
}
}
}
Class Navigate has two interger properties, fileCount and dirCount.
Properties provide encapsulation of data while supporting direct access. Each property has
a private integer field. It also provides two methods, get and set, which encapsulate that data.
The fileCount property is provided an explicit definintion, as shown to the right. The definition
provides a private int fileCount_ backing store, and getter and setter methods, shown explicitly.
Either, or both, of these methods can have additional code that modifies the method behaviors.
The dirCount property uses an implicit definition, where the compiler generates an anonymous
backing store, and provides default implementations for get and set, which have the same contents
as shown for the fileCount property. The benefit of using an implicit definition is that it is
quick to write and easy to understand.
Should, in the future, the class need to modify the
behaviors of the get or set methods, the dirCount property definition can be expanded, just like
the fileCount, and additional logic added. The important thing is, that making this change does
not affect the user interface for the class. We haven't broken any client code by changing the
property definition. That would not be the case for unencapsulated public data, a practice we
discourage.
The Navigate class uses the .Net System.IO namespace with very nicely designed File, FileInfo, Path,
and Directory classes. It also uses the DateTime class provided in the System namespace.
Finally, notice that the Navigate.go(path, pattern) method is used recursively. For each directory
go finds, it calls itself on that discovered path, passing the existing patterns, so that lower level
searches use the same search properties.
Make sure you look at
CodeSnap ClassDemo.
///////////////////////////////////////////////////////////////////////
/// Test.cs - Demonstrates use of System.IO classes ///
/// ver 1.1 ///
/// ///
/// Language: Visual C# ///
/// Platform: Dell Dimension 8100, Windows Pro 2000, SP2 ///
/// Application: CSE681 Example ///
/// Author: Jim Fawcett, CST 2-187, Syracuse Univ. ///
/// (315) 443-3948, jfawcett@twcny.rr.com ///
///////////////////////////////////////////////////////////////////////
//
// Operations:
// =============
// This is a test driver for Navigate. It simply extracts a path
// from the command line and calls Navigate.go(path).
using System;
using System.IO;
namespace Navigate
{
class Test
{
[STAThread]
static void Main(string[] args)
{
Console.Write("\n Demonstrate System.IO Classes ");
Console.Write("\n ===============================");
string path;
if(args.Length > 0)
path = args[0];
else
path = Directory.GetCurrentDirectory();
Navigate nav = new Navigate();
nav.go(path, "*.*");
Console.Write
(
"\n\n processed {0} files in {1} directories",
nav.fileCount, nav.dirCount
);
Console.Write("\n\n");
}
}
}
To the left you see the definition of the Test class, used while constructing Navigate
and later to demonstrate its capabilities to prospective users.
It starts by reading its command line, looking for a start path and patterns for file matching.
Then, it creates an instance of Navigate, called nav, and invokes its go(path, pattern) method.
When go completes its recursive walk through the directory tree rooted at path, Test displays
the number of files and directories processed.
Make sure you look at the
CodeSnap Class Demo.
More
///////////////////////////////////////////////////////////////////////
// ClassDemo.cs - Illustrate basic class operations //
// //
// Jim Fawcett, CSE681 - Software Modeling and Analysis, F2015 //
///////////////////////////////////////////////////////////////////////
/*
* A class definition does three important things for you:
* 1. Pulls together data and all the methods that directly manipulate
* it into one logical entity, making that code easier to understand.
* 2. Protects its data from being changed by any other code and so
* ensuring that the class data always stays in a valid state.
* 3. Allows using code to make multiple instances of the data, all
* operated on by the same methods, while still ensuring protection
* of the data's integrity.
*/
/*
* Read Chap 3 in the class text.
*/
using System; // +--------+
using System.Collections.Generic; // | Object |
using System.Linq; // +--------+
using System.Text; // ^
using System.Threading.Tasks; // |
// +-------+
namespace CSharpDemos // | Basic |
{ // +-------+
public class Basic
{
// class fields
private double valueType_ = 3.1415927;
private StringBuilder referenceType_ = new StringBuilder("a string");
// class properties
public double valueProperty { get; set; } = 3.2;
public StringBuilder referenceProperty { get; set; } =
new StringBuilder("will be converted to StringBuilder");
// constructor
public Basic(double d=1.0/3, string s="default string")
{
this.valueProperty = d;
this.referenceProperty = new StringBuilder(s);
}
// destructor is really a finalizer, run by the garbage collector
~Basic()
{
Console.Write("\n Basic instance is being finalized");
}
// member function
public void showIdentity()
{
// "this" is a reference to instance of the class that invoked method,
// e.g., basic1 and basic2, below. The code of each nonstatic member
// function needs to identify which instance to act upon.
Console.Write("\n instance identity is {0}", this.GetHashCode());
// GetHashCode() is a method inherited from "Object" class from which
// all reference types derive.
}
// member function
public void showState()
{
showIdentity();
Console.Write("\n value field: {0}", valueType_);
Console.Write("\n reference field: {0}", referenceType_);
Console.Write("\n value property: {0}", valueProperty);
Console.Write("\n reference property: {0}", referenceProperty);
}
}
// Extension methods are static methods of static classes that can be
// applied to instances of target type as if they were member functions.
// They can't access private or protected member data of target class
// but can uses its public interface.
static public class BasicExtensions
{
// the first argument type "this string" defines the target type
static public void title(this string astring, char underline = '-')
{
Console.Write("\n {0}", astring);
Console.Write("\n {0}", new string(underline, astring.Length + 2));
}
}
class ClassDemo
{
static void Main(string[] args)
{
"Demonstrating Basic Class".title();
Basic basic1 = new Basic();
Type t = basic1.GetType();
foreach (var method in t.GetMethods())
Console.Write("\n method: {0}", method);
Console.WriteLine();
"basic1 state".title();
basic1.showState();
Console.WriteLine();
"basic2 state".title();
Basic basic2 = new Basic(2.0/3, "another string");
basic2.showState();
Console.WriteLine();
"\n assigning reference types: basic2 = basic1".title();
basic2 = basic1;
BasicExtensions.title("now here are the resulting states"); // note
"basic1 state".title();
basic1.showState();
Console.WriteLine();
"basic2 state".title();
basic2.showState(); // now basic1 and basic2 handles refer to
Console.Write("\n\n"); // same object
}
}
}
The
source code to the left implements three classes.
The first, "Basic" contains two
private fields, and two public properties. It has methods:
- Constructor Basic(...)
- Destructor ~Basic()
- showIdentity()
- ShowState()
The constructor re-initializes its two properties. The destructor provides code for a finalizer
that runs when the .Net garbage collector disposes the class. Methods showIdentity() and showState()
provide information about the class and its internal state.
The second class, "BasicExtensions", illustrates an unusual capability. It creates
a method, "title(...)", that appears to extend the class of its target, in this case, the string class.
Without making any alterations to the string class, this method can be invoked from any string instance
that has been compiled in the same assembly as the extension class. You can see it being used in the
main method of the third class.
The third class, "ClassDemo" implements a single method "Main(...)" that provides
an entry point for the process, e.g., when executed, the process starts at the beginning of Main.
Demonstrating Basic Class
---------------------------
method: Double get_valueProperty()
method: Void set_valueProperty(Double)
method: System.Text.StringBuilder get_referenceProperty()
method: Void set_referenceProperty(System.Text.StringBuilder)
method: Void showIdentity()
method: Void showState()
method: System.String ToString()
method: Boolean Equals(System.Object)
method: Int32 GetHashCode()
method: System.Type GetType()
basic1 state
--------------
instance identity is 46104728
value field: 3.1415927
reference field: a string
value property: 0.333333333333333
reference property: default string
basic2 state
--------------
instance identity is 12289376
value field: 3.1415927
reference field: a string
value property: 0.666666666666667
reference property: another string
assigning reference types: basic2 = basic1
-----------------------------------------------
now here are the resulting states
-----------------------------------
basic1 state
--------------
instance identity is 46104728
value field: 3.1415927
reference field: a string
value property: 0.333333333333333
reference property: default string
basic2 state
--------------
instance identity is 46104728
value field: 3.1415927
reference field: a string
value property: 0.333333333333333
reference property: default string
Basic instance is being finalized
Basic instance is being finalized
Press any key to continue . . .
The output from "ClassDemo" is show here. The first few lines show information
about the methods of the "Basic" class, using reflection. Each .Net managed assembly
contains metadata including detailed information about all the types in the assembly.
Finally, we've shown the results of calling method "Basic.showState().
All .Net managed classes derive, either directly or indirectly, from a class called
"Object". The entire .Net library system is a hierarchy of classes, rooted at
this class.
We've used the "GetType()" method, inherited from class "Object",
to access the assembly's metadata, and used the "Type" class method, "GetMethods()"
to generate the full name of "Basic"'s methods.
Finally, we've shown the results of calling method "Basic.showState()".
What we're doing here is to illustrate how a fairly simple C# class works, and some of the processing
capability it inherits from the "System.Object" class.
One last thing to note: making the assignment "basic2 = basic1", as we did near the end of
"Main(...)", did not result in two instances
with identical state. The assignment actually redirects the basic2 handle to refer to the basic1 instance.
You can see that is what happened by observing that before the assignment they each had
unique identities - as indicated by their hashcodes. After the assignment, they each have the
same identity, e.g., the handles "basic1" and "basic2" refer to the same instance.