Friday, June 08, 2007

Free Ebooks

Absolutely free!!! For old book lovers this web site is treasure. Choose from variety of formats.

http://manybooks.net

Monday, May 21, 2007

.Net and Windows Security Part 1

This is three part series. Following is the breakdown of this series:

  1. First Part: General Windows Security Concepts
  2. Second Part: .Net classes for handling windows security
  3. Third Part: Sample code for adding/deleting user in windows shared folder security

Users and Groups

In Windows NT/2000/XP/2003 and now in VISTA every process runs in a security context. Every process is associated with a Windows Identity that is called a prinicipal. Whenever a process access a resource (file, directory, registry, event, mutex ..), principal is checked against resource's access rule. For now you can think resource's access rules as a table which identifies which user/group has which type of rights on the resource. In above line, I mentioned user/group because Windows presents notion of Group. Every user must belong to one or several groups.

Session Logon

A new Session Logon is created as soon as user logs in the system. Session contains security token. Whenever user launches a process (or process launch a new process), new process inherit security token and runs in security context of user or parent process. Windows automatically creates three session logon whenever it starts up: System Session, Local Session and Network Session.

Windows Security Identifier (SID)

Windows internally indentifies each user or group by its SID. It is a unique number and looks like a GUID, but actually this is not GUID. It has certain pattern which conforms to Security Descriptor Definition Language (SDDL). For example SID "S-1-5-21-790525478-1425521274-725345543-500" represent administrator account on my laptop. Usually SID which ends up with 500 belongs to built in administrator group. Windows has some default SIDs which are called Well-Known SIDs. Following list identifies some important Well-Known SIDs:

Anonymous Logon

(S-1-5-7)

A user who has connected to the computer without supplying a user name and password.

Authenticated Users

(S-1-5-11)

Includes all users and computers whose identities have been authenticated. Authenticated Users does not include Guest even if the Guest account has a password.

Everyone

(S-1-1-0)

On computers running Windows XP Professional, Everyone includes Authenticated Users and Guest. On computers running earlier versions of the operating system, Everyone includes Authenticated Users and Guest plus Anonymous Logon.

Terminal Server Users

(S-1-5-13)


Includes all users who have logged on to a Terminal Services server that is in Terminal Services version 4.0 application compatibility mode.


Windows Security Descriptor

Window Security Descriptor is the core of window security (at least from the developer point of view) and every resource must have security descriptor as soon as it is created. On msdn, you can find several documents explaining Win32 Security Descriptor in details. Following is the breakdown of SD structure:

  • Owner SID
  • Group (optional, adopted from posix security structure)
  • Control Flags
  • DACL (Discretionary access control list)
  • SACL (System access control list)

DACL

DACL is the ordered list/array of Access Control Elements (ACE). ACE is basically the rights or permissions assigned to user or group. There are two type of ACE: Allow and Deny. It can be understand by a very basic example. Suppose a user has only read-allow permission on a directory. This "only read-allow" permission cannot stop him from creating a new file/directory or even from deleting the directory. For proper read only permissions, user must be given "write-deny" permission also. It is important to note that ACEs are evaluated in the order that they are stored in the DACL. Windows does not necessarily evaluate all ACEs during an access right request.

SACL

SACL is another list of ACE. In normal scenarios, developer may not need to handle this list. ACEs in SACL are used for audit permissions. For example: rights grant event should be logged or not.


Friday, May 18, 2007

Office 2007 - I’m Love’n it

Well, besides irresistible sleek user interface, Office 2007 is in true sense a developer's package. I have following point to support my statement:

  1. Following OpenXml standards as well as supports MS proprietary format. So being a XML expert, I can play with package in my way.
  2. Developer Band: Now Word and excel are not toys for only managers. Right!!
  3. Word: Excellent support for blogging. I can write a complete blog in word and publish it to my blogging site even without going on that (I wrote this blog offline, when I was just going to sleep)
  4. Outlook: Excellent RSS Feed functionality, you can even have full post as an attachment.

Happy Blogging.

Friday, May 11, 2007

Deep Serialization using MemoryStream

I found this quite easy to use MemoryStream and BinaryFormatter object to provide deep copy functionality.

For full article, please visit : http://www.c-sharpcorner.com/UploadFile/sd_surajit/cloning05032007012620AM/cloning.aspx

Following is the code snippet for quick look:

using System.IO;

using System.Runtime.Serialization.Formatters.Binary;



public
Class Test : IClonable

{

public Test()

{

}

// deep copy in separeate memory space

public
object Clone()

{

MemoryStream ms = new MemoryStream();

BinaryFormatter bf = new BinaryFormatter();

bf.Serialize(ms, this);

ms.Position = 0;

object obj = bf.Deserialize(ms);

ms.Close();

return obj;

}

}

Tuesday, May 08, 2007

A Custom Generic Collection which has List as well as Dictionary functionality for Custom Entity Classes

I frequently need a collection which List as well as Dictionary functionality for custom entity classes. So I wrote following generic class which provides the same functionality. There is one prerequisite for this class: Custom entity class should have override function ToString() which actually returns a unique key for this collection. Following is the code for Custom class:

using System;

using System.Collections.Generic;

public
class
CC<T> : IEnumerable<T>

{


public
List<T> List = new
List<T>();


public
Dictionary<string, T> Dict = new
Dictionary<string, T>();


public
void Add(T obj)

{

List.Add(obj);

Dict.Add(obj.ToString(), obj);

}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()

{


return PRIVGetEnumerator();

}


IEnumerator<T> IEnumerable<T>.GetEnumerator()

{


return PRIVGetEnumerator();

}



private
IEnumerator<T> PRIVGetEnumerator()

{


foreach (T obj in List)


yield
return obj;

}



public
bool Contains(String key)

{


return (Dict.ContainsKey(key));

}



public
void Remove(String key)

{

Dict.Remove(key);


for (int i = 0; i < List.Count; i++)

{


if (List[i].ToString().Equals(key))

{

List.RemoveAt(i);


break;

}

}

}



public
void RemoveAt(int index)

{

Dict.Remove(List[index].ToString());

List.RemoveAt(index);

}



public T this[int index]

{


get

{


return
this.List[index];

}


set

{

List[index] = value;


if (!Dict.ContainsKey(value.ToString()))

Dict.Add(value.ToString(), value);

}

}



public T this[string key]

{


get

{


return Dict[key];

}


set

{


if (!Dict.ContainsKey(key))

{

Dict[key] = value;

List.Add(value);

}


}

}



public
void CopyTo(T[] array, int index)

{

List.CopyTo(array, index);

}



public
void AddRange(List<T> value)

{


for (int i = 0; (i < value.Count); i = (i + 1))

{


this.Add(value[i]);

}

}



public
Dictionary<string, T>.KeyCollection Keys

{


get

{


return (Dict.Keys);

}

}



public
Dictionary<string, T>.ValueCollection Values

{


get

{


return (Dict.Values);

}

}



}


Following is the example of custom entity class:

using System;

using System.Xml.Serialization;

public
class
Person

{


private
string _id = "";


///
<summary>


/// Set or Get _id


///
</summary>

[XmlElement(ElementName = "Id")]


public
string Id

{


set { this._id = value; }


get { return
this._id; }

}



private
string _FirstName = "";


///
<summary>


/// Set or Get _FirstName


///
</summary>

[XmlElement(ElementName = "FirstName")]


public
string FirstName

{


set { this._FirstName = value; }


get { return
this._FirstName; }

}



private
string _LastName = "";


///
<summary>


/// Set or Get _LastName


///
</summary>

[XmlElement(ElementName = "LastName")]


public
string LastName

{


set { this._LastName = value; }


get { return
this._LastName; }

}



public
override
string ToString()

{


return
this.Id;

}


public Person(string id, string fName, string lName)

{


this.Id = id;


this.FirstName = fName;


this.LastName = lName;

}


}

Finally, following is the code snippet which shows how we can use this custom generic class:

class
Program

{


static
void Main(string[] args)

{



CC<Person> myCC = new
CC<Person>();

myCC.Add(new
Person("1", "Tom", "Hanks"));

myCC.Add(new
Person("2", "Julia", "Roberts"));

myCC.Add(new
Person("3", "Johny", "Depp"));


myCC[1] = new
Person("2", "Orlando", "Bloom");

myCC["4"] = new
Person("4", "Leonardo", "Dacaprio");

myCC.RemoveAt(0);



foreach (Person p in myCC)


Console.WriteLine(p.FirstName);


Console.ReadLine();

}

}


Happy Movies!!!


Tuesday, April 24, 2007

Test Post from Word 2007

This is test blog using Word 2007

Wednesday, March 28, 2007

Getting correct object type

Following code snippet demonstrates how can we use GetType function to get correct object type in inheritance hierarchy

public interface A
{
void print();

}

public class B : A
{
void A.print()
{
Console.WriteLine("B");
}
}

public class C : A
{
void A.print()
{
Console.WriteLine("C");
}
}


public class MyClass
{
public static void Main()
{
A a = new C();
Console.WriteLine("#: " + a.GetType().Name);
a.print();
RL();
}

}

Tuesday, March 27, 2007

SMTP Managed Event Sink for scanning incoming emails

I want to scan all incoming mails using Managed Event sink. I am implementing IMailTransportSubmission Interface.

void IMailTransportSubmission.OnMessageSubmission(
MailMsg message,
IMailTransportNotify notify,
IntPtr context)
{
try
{
// Fill details of the Message in database
FillData(message);
}
catch (Exception ex)
{
EventLog.WriteEntry(
Assembly.GetExecutingAssembly().FullName,
ex.Message + "\n" + ex.StackTrace.ToString(),
EventLogEntryType.Error);
}
finally
{
if (null != message)
Marshal.ReleaseComObject(message);

}
}


I am using following command to bind the event sink:

cscript smtpreg.vbs /add 1 OnTransportSubmission "MyEventSink.ManagedSink" MyEventSink.ManagedSink.Sink "MAIL FROM=*" 28001

I have a development exchange server. When I put a message in “pickup” directory, a entry for this message is inserted into database. That means my event sink working fine, but I am not sure how to test it for incoming message as this development exchange doesn’t have dns entries and I have to check event sink functionality for incoming mail from same development server somehow. First thing I want to know that whether I have implemented correct interface for scanning incoming mails. Two, whether I have registered correct event for incoming mails.

Tuesday, March 06, 2007

SharePoint Versioning Algorithm

In my current project I need to implement versioning of the document stored in the SQL Server in the same way as SharePoint. I wrote following C# code to simulate versioning of SharePoint:

    int majorVersion = 0;
int maxMajorVersion = 2;
int maxMinorVersion = 20; // default 511;
string version = string.Empty;
int counter = 1;
int id = 1;

int maxId = 512 * maxMajorVersion;

List<int> lstNxtVersionIds = new List<int>();
for (int i = 1; i <= maxMajorVersion; i++)
lstNxtVersionIds.Add(i*512);

for (int i = 1; i<=maxId & id<=maxId; i++)
{
if (counter == maxMinorVersion)
{
version = majorVersion.ToString() + "." + counter.ToString();
Console.WriteLine ("id {0}, version {1}", id, version);
if (majorVersion < maxMajorVersion)
{
majorVersion++;

foreach (int nxtVersionId in lstNxtVersionIds)
{
if (id < nxtVersionId)
{
id = nxtVersionId;
break;
}
}
}
else if (majorVersion == maxMajorVersion)
break;
counter = 0;
}
version = majorVersion.ToString() + "." + counter.ToString();
Console.WriteLine ("id {0}, version {1}", id, version);

id++;
counter++;

Thursday, February 22, 2007

XML and SQL Server 2005

Here are two interesting articles on usage of XML in SQL 2005:

Some Usages for XML

I've Got the XML - Now What?

Wednesday, January 31, 2007

Friday, January 19, 2007

C# Passing Comma Seperated Stored Procedure Paramter

I came across passing a very long comma seperated string as a Stored Procedure Parameter. There is character limit on SqlParameter, that is one. The second thing I didn't want to pass individual string as parameters and calling same Stored Procedure for each string. That would have been clear and present performance hit. So I wrote following piece of code to avoid this situation. Break large string into small strings that can be accepted by SqlParameter.

string tmpMatterSpaceNames = string.Empty;
int lowerLimit = Constants.SqlParamCharLimit - 200;
StringCollection objStrCollection = new StringCollection();
foreach (string matterSpaceName in objNV.AllKeys)
{
tmpMatterSpaceNames += "'" + matterSpaceName + "',";
if (tmpMatterSpaceNames.Length > lowerLimit)
{
tmpMatterSpaceNames = Regex.Replace(tmpMatterSpaceNames, @",$", "");
objStrCollection.Add(tmpMatterSpaceNames);
tmpMatterSpaceNames = string.Empty;
}
}
if (tmpMatterSpaceNames != string.Empty)
{
tmpMatterSpaceNames = Regex.Replace(tmpMatterSpaceNames, @",$", "");
objStrCollection.Add(tmpMatterSpaceNames);
}

foreach (string matterSpaceNames in objStrCollection)
objMSDao.UpdateSourceCreatedFlag (matterSpaceNames, true);

Saturday, January 13, 2007

A Day in the Life of Indian Village

In Pragati Maidan Delhi, I find this place pretty interesting. This place presents models of indian villages across the different cultures. You can also see old household, crockeries and bull carts here. Posted by Picasa

Friday, December 22, 2006

C#: Random selection of elements in a list, with no repeats

Following code snippet shows how we can randomize selection of element in a list in effective manner:

System.Collections.Generic.List intList = new System.Collections.Generic.List();
intList.Add(1);
intList.Add(2);
intList.Add(3);
intList.Add(4);
intList.Add(5);

int size = intList.Count;
while (size>0)
{
size--;
int index = (new Random()).Next(0,size);
int elem = intList[index];
intList[index] = intList[size];
Console.WriteLine(elem.ToString());
}

Monday, December 18, 2006

Scala: A new programming paradigm

Scala is a modern multi-paradigm programming language designed to express common programming patterns in a concise, elegant, and type-safe way. It smoothly integrates features of object-oriented and functional languages.

Following are the highlights of this new language:
  • Object Oriented
  • Functional
  • Statically (strongly) typed
  • Extensible
  • Integration with .NET and Java Platforms
  • Built-in excellent pattern matching for Strings as well as other types like XML.
Please also read:
Why does the world need another programming language?

Thursday, December 14, 2006

Regex for atleast one alphabet and one number in string


This above Regular Expression uses Positive look ahead and Positive look behind to validate the conditions.

Wednesday, December 13, 2006

Perl: Free SQL Dependency Tracker

I am very impressed with RedGate SQl Depenency Tracker . This tool helped me lot in performing impact analysis of database object changed. Following Perl script is crude version of this tool. For given database object, it tracks dependent Stored Procedures, Functions, Views and Triggers drilled down to 3 levels. The only problem with this script it requires ODBC connection to Database.


#!/usr/bin/perl
use strict;
use DBI;

my $dbh = DBI->connect( "DBI:ODBC:localhost", 'sa', 'sa', { PrintError => 0 } );
die "Unable for connect to server $DBI::errstr"
unless $dbh;

my $search_object = $ARGV[0];

my $query = qq {
SELECT Name FROM dbo.sysobjects WHERE Xtype in ('P', 'FN', 'IF', 'V', 'TR', 'TF')
};

#print "Executing query: $query\n";
my $sth = $dbh->prepare($query)
or die "Couldn't prepare statement: " . $dbh->errstr;

$sth->execute or die "Couldn't execute statement: " . $sth->errstr;

my @object_array;
my %dbobjects;
while ( my @row = $sth->fetchrow_array ) {
if ( $row[0] !~ /^(dt_sys)/ ) {
push @object_array, $row[0];

}
}
$sth->finish;

foreach my $object (@object_array) {
my $text_query = qq {sp_helptext $object};
my $sth = $dbh->prepare($text_query)
or die "Couldn't prepare statement: " . $dbh->errstr;
$sth->execute or die "Couldn't execute statement: " . $sth->errstr;
my $text = '';
while ( my @row_text = $sth->fetchrow_array ) {

foreach (@row_text) {
$_ =~ s/--.*$//;
$text .= $_;
}

}
$text =~
s#/\*[^*]*\*+([^/*][^*]*\*+)*/("(\\.[^"\\])*"'(\\.[^'\\])*'.[^/"'\\]*)#defined $2 ? $2 : ""#gse;
$text =~ s/create.*?AS//msgi;
$dbobjects{$object} = $text;
$sth->finish;

#print $text, "\n--------------------------------------\n";
}

my @Level1 = FindDepenency($search_object);
foreach (@Level1) {
print $_, "\n";
my @Level2 = FindDepenency($_);
foreach (@Level2) {
print "------$_\n";
my @Level3 = FindDepenency($_);
foreach (@Level3) {
print "\t\t------$_\n";
}
}
}

sub FindDepenency {
my $key = shift;
my @return_arr;
foreach my $dbo ( keys %dbobjects ) {
next if ( lc($dbo) eq lc($key) );
if ( $dbobjects{$dbo} =~ /$key/msig ) {
push @return_arr, $dbo;
}
}
return @return_arr;
}

# Disconnect the database from the database handle.
$dbh->disconnect;

Thursday, December 07, 2006

Perl: Finding duplicate files

Sometimes we need to track duplicates files (same file with different name or path) in directory hierarchy. Following PERL script finds duplicate file in given directory.


#!/usr/bin/perl
use strict;
use warnings;
use File::Find;
use Digest::MD5;

###########################################################

# find_dups(@dir_list) -- Return an array containing a list
# of duplicate files.
###########################################################
sub find_dups(@)
{
# The list of directories to search
my @dir_list = @_;

# If nothing there, return nothing
if ($#dir_list < 0) {
return (undef);
}

my %files; # Files indexed by size

# Go through the file tree and find all
# files with a similar size
find(sub {
-f &&
push @{$files{(stat(_))[7]}}, $File::Find::name
}, @dir_list
);

my @result = (); # The resulting list

# Now loop through the list of files by size and see
# if the md5 is the same for any of them
foreach my $size (keys %files) {
if ($#{$files{$size}} < 1) {
next;
}
my %md5; # MD5 -> file name array hash

# Loop through each file of this size and
# compute the MD5 sum
foreach my $cur_file (@{$files{$size}}) {
# Open the file. Skip the files we can't open
open(FILE, $cur_file) or next;
binmode(FILE);
push @{$md5{
Digest::MD5->new->addfile(*FILE)->hexdigest}
}, $cur_file;
close (FILE);
}
# Now check for any duplicates in the MD5 hash
foreach my $hash (keys %md5) {
if ($#{$md5{$hash}} >= 1) {
push(@result, [@{$md5{$hash}}]);
}
}
}
return @result
}

# my @dups = find_dups(@ARGV);
my @dir = ('C:\tmp');
my @dups = find_dups(@dir);

foreach my $cur_dup (@dups) {
print "Duplicates\n";
foreach my $cur_file (@$cur_dup) {
print "\t$cur_file\n";
}
}

Tuesday, December 05, 2006

Video of Lifetime

This is video of a lifetime...http://www.metacafe.com/watch/277085/everyone_must_see_this/

lyrics -

Everybody's free to use sunscreen....Ladies and Gentlemen of the class of ’99 If I could offer you only one tip for the future, sunscreen would beit. The long term benefits of sunscreen have been proved byscientists whereas the rest of my advice has no basis more reliablethan my own meanderingexperience…I will dispense this advice now. Enjoy the power and beauty of your youth; oh nevermind; you will notunderstand the power and beauty of your youth until they have faded.But trust me, in 20 years you’ll look back at photos of yourself andrecall in a way you can’t grasp now how much possibility lay beforeyou and how fabulous you really looked….You’re not as fat as youimagine. Don’t worry about the future; or worry, but know that worrying is aseffective as trying to solve an algebra equation by chewingbubblegum. The real troubles in your life are apt to be things thatnever crossed your worried mind; the kind that blindside you at 4pmon some idle Tuesday. Do one thing everyday that scares you Sing Don’t be reckless with other people’s hearts, don’t put up withpeople who are reckless with yours. Floss Don’t waste your time on jealousy; sometimes you’re ahead, sometimesyou’re behind…the race is long, and in the end, it’s only withyourself. Remember the compliments you receive, forget the insults; if yousucceed in doing this, tell me how. Keep your old love letters, throw away your old bank statements. Stretch Don’t feel guilty if you don’t know what you want to do with yourlife…the most interesting people I know didn’t know at 22 what theywanted to do with their lives, some of the most interesting 40 yearolds I know still don’t. Get plenty of calcium. Be kind to your knees, you’ll miss them when they’re gone. Maybe you’ll marry, maybe you won’t, maybe you’ll have children,maybeyou won’t, maybe you’ll divorce at 40, maybe you’ll dance the funkychicken on your 75th wedding anniversary…what ever

Regex for Good and bad values


Above regex matches GOOD and doesnot match if string contains BAD.

Thursday, November 23, 2006

SQL: Using while loop for replacing cursor

Following SQL code snippet shows how we can use while loop :


create table #t (pk_counter int identity(1,1), pk_subquestion int, fk_question int, subquestion_text varchar (500), subquestion_type varchar (50), subquestion_order varchar(50), required_flag bit)

declare @counter int
declare @maxcount int
declare @subquestionid int
select @maxcount = count(pk_counter) from #t
set @counter = 1


while (@counter < @maxcount + 1) begin select @subquestionid=pk_subquestion from #t where pk_counter=@counter exec stp_get_options @subquestionid set @counter = @counter + 1 end
drop table #t

SQL: Finding duplicate rows in table

Following SQL code snippet demonstrates finding duplicate rows in table:

SELECT SampleDescription,
COUNT(SampleDescription) AS Occurance
FROM SchedTimeOffType
GROUP BY SampleDescription
HAVING ( COUNT(SampleDescription) > 1 ) order by Occurance desc

Tuesday, November 07, 2006

SQL: Recursive Select Variable

Using this method we can convert a vertical list into horizontal list. Atleast two uses are suggested by Paul Nielson:
  1. Denormalizing a list
  2. Dynamic Cross tabs query

In following example we will see how can we denormalize a list i.e. converting a vertical list into horizontal list.

use NorthWind;
DECLARE @Name nvarchar (2200)
SET @Name = ''
SELECT @Name = @Name + A.Name + '; ' FROM (Select DISTINCT CompanyName AS Name FROM Customers) AS A
PRINT @Name

Thursday, November 02, 2006

Getting Caller Function Name

Sometimes it would be great to know which is the caller function for the current executing function for debugging purpose. Following code snippet shows how to get caller function name:

StackTrace stackTrace = new StackTrace();
StackFrame stackFrame = stackTrace.GetFrame(1);
MethodBase methodBase = stackFrame.GetMethod();
Console.WriteLine(methodBase.Name);

Wednesday, August 23, 2006

C#: Event Model and Observer pattern

Observer pattern can be made easily using C# multicast deletgates. We will define two classes Publisher and Subscriber. Publisher is the class which publish the 'event'. Subscriber class subscribes the event and whenever event is called, subscriber is notified. Subscriber can have function which can be invoked in response to event. This function is called event handler. Publisher-Subscriber model can be built on two methodologies - Pull and Push.
In Pull, subscriber has permission to 'see' data/state of Publisher.
In Push, Publisher notify subscriber with only most relevant data/state. Here I am going to implement Push Model.


//Code
       public class Publisher2
{
// EventHandler delegate is defined .NET as
// public delegate void EventHandler (object obj, EventArgs e);
public event EventHandler onTick;

private void NotifyOnTick(EventArgs e)
{
if (onTick != null)
{
onTick(this, e);
}

}

public void StartClock()
{
int tick = 1;
while (tick <= 10) { System.Threading.Thread.Sleep(1000); NotifyOnTick(new MyEventArgs(tick));
tick++;
}
}

}

public class MyEventArgs : EventArgs
{
private int tick;
public int Tick
{
get { return this.tick;}
}

public MyEventArgs(int tick)
{
this.tick = tick;
}

}

public class Subscriber2
{
private int subsriberID;
public Subscriber2(Publisher2 publisher, int id)
{
this.subsriberID = id;
publisher.onTick += new EventHandler(this.DisplayOnTick);
}
public void DisplayOnTick(object obj, EventArgs e)
{
Console.Write("Subscriber [{0}]: ", this.subsriberID);
MyEventArgs me = e as MyEventArgs;
Console.WriteLine(me.Tick.ToString());
}
public void Unsubscribe(Publisher2 publisher)
{
publisher.onTick -= new EventHandler(this.DisplayOnTick);
}
}


class Class1
{
///
/// The main entry point for the application.
///
[STAThread]
static void Main(string[] args)
{

Publisher2 publisher = new Publisher2();
Subscriber2 sbc1 = new Subscriber2(publisher,1);
Subscriber2 sbc2 = new Subscriber2(publisher,2);
publisher.StartClock();
sbc2.Unsubscribe(publisher);
publisher.StartClock();
}
}
//Output
Subscriber [1]: 1
Subscriber [2]: 1
Subscriber [1]: 2
Subscriber [2]: 2
Subscriber [1]: 3
Subscriber [2]: 3
Subscriber [1]: 4
Subscriber [2]: 4
Subscriber [1]: 5
Subscriber [2]: 5
Subscriber [1]: 6
Subscriber [2]: 6
Subscriber [1]: 7
Subscriber [2]: 7
Subscriber [1]: 8
Subscriber [2]: 8
Subscriber [1]: 9
Subscriber [2]: 9
Subscriber [1]: 10
Subscriber [2]: 10
Subscriber [1]: 1
Subscriber [1]: 2
Subscriber [1]: 3
Subscriber [1]: 4
Subscriber [1]: 5
Subscriber [1]: 6
Subscriber [1]: 7
Subscriber [1]: 8
Subscriber [1]: 9
Subscriber [1]: 10