How to get a table from a SNMP Agent using SNMPv2c and GetBulk

using System;
using System.Net;
using System.Text;
using System.Collections.Generic;
using SnmpSharpNet;
 
namespace SnmpTable
{
	class MainClass
	{
		public static void Main(string[] args)
		{
			if( args.Length != 3 )
			{
				Console.WriteLine("Syntax: SnmpTable.exe <host> <community> <table oid>");
				return;
			}
			Dictionary<String, Dictionary<uint, AsnType>> result = new Dictionary<String, Dictionary<uint, AsnType>>();
			// Not every row has a value for every column so keep track of all columns available in the table
			List<uint> tableColumns = new List<uint>();
			// Prepare agent information
			AgentParameters param = new AgentParameters(SnmpVersion.Ver2, new OctetString(args[1]));
			IpAddress peer = new IpAddress(args[0]);
			if( ! peer.Valid ) {
				Console.WriteLine("Unable to resolve name or error in address for peer: {0}", args[0]);
				return;
			}
			UdpTarget target = new UdpTarget((IPAddress)peer);
			// This is the table OID supplied on the command line
			Oid startOid = new Oid(args[2]);
			// Each table OID is followed by .1 for the entry OID. Add it to the table OID
			startOid.Add(1); // Add Entry OID to the end of the table OID
			// Prepare the request PDU
			Pdu bulkPdu = Pdu.GetBulkPdu();
			bulkPdu.VbList.Add(startOid);
			// We don't need any NonRepeaters
			bulkPdu.NonRepeaters = 0;
			// Tune MaxRepetitions to the number best suited to retrive the data
			bulkPdu.MaxRepetitions = 100;
			// Current OID will keep track of the last retrieved OID and be used as 
			//  indication that we have reached end of table
			Oid curOid = (Oid)startOid.Clone();
			// Keep looping through results until end of table
			while( startOid.IsRootOf(curOid) )
			{
				SnmpPacket res = null;
				try {
					res = target.Request(bulkPdu, param);
				} catch( Exception ex ) {
					Console.WriteLine("Request failed: {0}", ex.Message);
					target.Close();
					return;
				}
				// For GetBulk request response has to be version 2
				if( res.Version != SnmpVersion.Ver2 ) {
					Console.WriteLine("Received wrong SNMP version response packet.");
					target.Close();
					return;
				}
				// Check if there is an agent error returned in the reply
				if( res.Pdu.ErrorStatus != 0 ) {
					Console.WriteLine("SNMP agent returned error {0} for request Vb index {1}",
					                  res.Pdu.ErrorStatus, res.Pdu.ErrorIndex);
					target.Close();
					return;
				}
				// Go through the VbList and check all replies
				foreach( Vb v in res.Pdu.VbList ) {
					curOid = (Oid)v.Oid.Clone();
					// VbList could contain items that are past the end of the requested table.
					// Make sure we are dealing with an OID that is part of the table
					if( startOid.IsRootOf(v.Oid) ) {
						// Get child Id's from the OID (past the table.entry sequence)
						uint[] childOids = Oid.GetChildIdentifiers(startOid, v.Oid);
						// Get the value instance and converted it to a dotted decimal
						//  string to use as key in result dictionary
						uint[] instance = new uint[childOids.Length-1];
						Array.Copy(childOids, 1, instance, 0, childOids.Length-1 );
						String strInst = InstanceToString(instance);
						// Column id is the first value past <table oid>.entry in the response OID
						uint column = childOids[0];
						if( ! tableColumns.Contains(column) )
							tableColumns.Add(column);
						if( result.ContainsKey(strInst) ) {
							result[strInst][column] = (AsnType)v.Value.Clone();
						} else {
							result[strInst] = new Dictionary<uint, AsnType>();
							result[strInst][column] = (AsnType)v.Value.Clone();
						}
					} else {
						// We've reached the end of the table. No point continuing the loop
						break;
					}
				}
				// If last received OID is within the table, build next request
				if( startOid.IsRootOf(curOid) ) {
					bulkPdu.VbList.Clear();
					bulkPdu.VbList.Add(curOid);
					bulkPdu.NonRepeaters = 0;
					bulkPdu.MaxRepetitions = 100;
				}
			}
			target.Close();
			if( result.Count <= 0 ) {
				Console.WriteLine("No results returned.\n");
			} else {
				Console.Write("Instance");
				foreach( uint column in tableColumns ) {
					Console.Write("\tColumn id {0}", column);
				}
				Console.WriteLine("");
				foreach( KeyValuePair<string,Dictionary<uint,AsnType>> kvp in result ) {
					Console.Write("{0}", kvp.Key);
					foreach( uint column in tableColumns ) {
						if( kvp.Value.ContainsKey(column) ) {
							Console.Write("\t{0} ({1})", kvp.Value[column].ToString(),
							                  SnmpConstants.GetTypeName(kvp.Value[column].Type));
						} else {
							Console.Write("\t-");
						}
					}
					Console.WriteLine("");
				}
			}
		}
		public static string InstanceToString(uint[] instance) {
			StringBuilder str = new StringBuilder();
			foreach( uint v in instance ) {
				if( str.Length == 0 )
					str.Append(v);
				else
					str.AppendFormat(".{0}", v);
			}
			return str.ToString();
		}
	}
}

Here is example output of the above program when retrieving hosts ARP table:

SnmpTable.exe localhost public 1.3.6.1.2.1.4.22
Instance	Column id 1	Column id 2	Column id 3	Column id 4
5.167.189.69.140	5 (Integer32)	58 6D 8F 06 81 3F (OctetString)	167.189.69.140 (IPAddress)	1 (Integer32)
5.167.189.69.213	5 (Integer32)	C8 60 00 54 AF 9A (OctetString)	167.189.69.213 (IPAddress)	1 (Integer32)
5.167.189.69.231	5 (Integer32)	14 5A 05 D7 1A 46 (OctetString)	167.189.69.231 (IPAddress)	1 (Integer32)
5.167.189.69.246	5 (Integer32)	00 90 A9 C0 5B FE (OctetString)	167.189.69.246 (IPAddress)	1 (Integer32)
5.167.189.69.249	5 (Integer32)	C8 33 4B 38 88 10 (OctetString)	167.189.69.249 (IPAddress)	1 (Integer32)
5.167.189.69.254	5 (Integer32)	00 25 90 34 5A 6A (OctetString)	167.189.69.254 (IPAddress)	1 (Integer32)
5.167.189.69.255	5 (Integer32)	FF FF FF FF FF FF (OctetString)	167.189.69.255 (IPAddress)	1 (Integer32)