What follows is a discussion of my experience with .NET generics & the
..NET framework (as implemented in the Visual Studio 2005 Beta 1),
which leads to questions as to why certain things are the way they
are.
***** Summary & Questions *****
In a nutshell, the current .NET generics & .NET framework make it
sometimes difficult or even impossible to write truly generic code.
For example, it seems to be impossible to write a truly generic
Complex class or a truly generic Matrix class (see below for details).
Since this seems to be due to a combination of shortcomings in both
the .NET framework and the generics mechanism, I have two questions:
1. Why does the overload resolution mechanism only consider overloads
with generic parameters when we call a function with a generic
argument? If it considered other overloads as well, this would allow
us to work around classes that do not implement the proper generic
interfaces (see below for an example).
2. Why do .NET framework classes for primitive types (Byte, Int32,
Decimal, Double, etc.) not implement an interface like IArithmetic< T (see below for an example)?
In isolation, neither of these limitations would be a big problem, as
we could easily work around them. However, their combination seems to
make it impossible to use generics for some tasks.
***** Discussion *****
Let us try to develop a generic Complex class in C# with generics. Our
(admitedly naive) first shot would be as follows:
public struct Complex< T >
{
T real;
T imag;
public Complex( T real, T imag )
{
this.real = real;
this.imag = imag;
}
public static Complex< T > operator +(
Complex< T > left, Complex< T > right )
{
return new Complex< T >(
left.real + right.real, left.imag + right.imag );
}
// other operators
}
Trying to compile this yields the following errors:
complex.cs(24,7 ): error CS0019: Operator '+' cannot be applied to
operands of type 'T' and 'T'
complex.cs(24,3 1): error CS0019: Operator '+' cannot be applied to
operands of type 'T' and 'T'
which is fair enough, as we didn't specify any constraints for T.
However, exactly what constraint should we specify to make this work?
After all we want to use our Complex class with primitive types (e.g.
float, double) *and* user-defined types. To make it usable for the
latter is easy enough:
public interface IArithmetic< T >
{
T Add( T other );
// other operations
}
public struct Complex< T > where T : IArithmetic< T >
{
T real;
T imag;
public Complex( T real, T imag )
{
this.real = real;
this.imag = imag;
}
public static Complex< T > operator +(
Complex< T > left, Complex< T > right )
{
return new Complex< T >(
left.real.Add( right.real ),
left.imag.Add( right.imag ) );
}
// other operators
}
So far so good, but what do we do to make this work for types that do
not implement our interface, namely framework-supplied ones like
float, double and perhaps decimal? All these types only implement the
interfaces IComparable, IFormattable, IConvertible and IComparable< T. None of these helps us implementing Complex. After some fiddling
around, I came up with the following:
public interface IArithmetic< T >
{
T Add( T other );
}
public struct Complex< T >
{
T real;
T imag;
public Complex( T real, T imag )
{
this.real = real;
this.imag = imag;
}
public static Complex< T > operator+(
Complex< T > left, Complex< T > right )
{
return new Complex< T >(
Add( left.real, right.real ),
Add( left.imag, right.imag ) );
}
// other operators
static float Add( float left, float right )
{
return left + right;
}
// other overloads for double, etc.
// overload for all other types
static U Add< U >( U left, U right )
{
// implementation postponed
}
}
I was hoping that the two Add overloads would allow me to discriminate
between primitive, framework-supplied types and user-supplied types.
This does not work, the generic overload is also selected when I add
two Complex< float > objects.
I then tried various other ways to discriminate between types, but
none of them led to anything more interesting than the above. It seems
that when you call a function with an argument of generic type that
the parameter of the function accepting said argument inevitably also
needs to be a generic type. I.e. the second Add overload can take two
forms:
static T Add( T left, T right )
or
static U Add< U >( U left, U right )
It seems that no matter which form we choose, no other Add overloads
(e.g. for float, etc.) will ever be considered.
Nov 16 '05
17 3325
"Jay B. Harlow [MVP - Outlook]" <Ja************ @msn.com> wrote in message
news:uA******** ******@TK2MSFTN GP10.phx.gbl... Daniel, Maybe its late ;-)
By the time I write all the dynamic methods for all the types I would want, wouldn't it be quicker or at least just as easy to simply write a code generator based on the CodeDom to spit out all the Arithmetic proxy classes based on what I stated??
Probably, ;). Its all a matter of what you need and want to subject your
users to. Personally I'd take the approach that *doesn't* use extra
interfaces externally if possible. It'll confuse less people and put less
strain on existing OM's.
The only issue is, the CodeDom approach won't support extra types, the
dynamic methods approach will work with *all* types that support the
operator in question, even if they are written by the end user. CodeDom is a
bit tricky there.
Granted, the CodeDom approach gives you a nice way to constrain types.
It ends up being whichever best supports your needs.
Otherwise I'm keeping the code snippet it may come in handy for other things...
Hopefully it will. Dynamic Methods are probably my favorite things about
..NET 2.0, ;).
Andreas,
If you haven't you may want to submit a suggestion at: http://lab.msdn.microsoft.com/vs2005/
I see at least one suggestion: http://lab.msdn.microsoft.com/produc...5-9c64cce45d2a
Related to this problem.
I was actually thinking of the Generic Delegate combined with an anonymous
method on possible the constructor...
Hope this helps
Jay
"Andreas Huber" <ah****@gmx.net > wrote in message
news:40******** **@news.bluewin .ch... I sincerely hope you did not mean "really bad one" as bad as it sounds. :-| Sorry, that was not my intention. I didn't write "really bad" because I think it's a bad idea to solve the problem this way (under the circumstances, it might even be the best way). I wrote it because I had
high hopes that .NET generics will be a powerful tool to design our programs better and avoid most of the code duplication that's currently necessary. I'm a bit disappointed to see that I was hoping for too much. Generics do solve a few problems (namely performance and compile-time correctness
stuff) but they still only partially solve that nasty code duplication problem. I also think the guys at Microsoft must be aware of this but they did not do something about it. I'm sure they have their reasons but it would be nice
to know them (including easy work-arounds we might be missing).
A second alternative I've wondered how usable would be Generic Delegates, only I'm not sure how easy they would be to use in this case.
Action<T> is an example of a generic delegate:
http://lab.msdn.micros oft.com/library/default.asp?url =/library/en-us/cpref/html/T_System_Action `1.asp Generic delegates might indeed offer a way to specify how to perform the operations of the generic type. But then again the same can be done with
an interface:
interface IArithmeticOps< T > { T Add( T left, T right ); // other ops }
struct Complex< T, Ops > where U : IArithmeticOps< T >, new() { static Ops ops = new Ops(); T real; T imag;
// ...
public static T operator+( T left, T right ) { return new Complex< T, Ops >( ops.Add( left.real, right.real ), ops.Add( left.imag, right.imag ) ); } }
Regards,
Andreas
"Jay B. Harlow [MVP - Outlook]" <Ja************ @msn.com> wrote in message
news:Oo******** ******@TK2MSFTN GP12.phx.gbl... Andreas, If you haven't you may want to submit a suggestion at:
http://lab.msdn.microsoft.com/vs2005/
I see at least one suggestion:
http://lab.msdn.microsoft.com/produc...5-9c64cce45d2a
Unfortunatly that solution is pretty much *entirely* against the semantics
of C# as it stands today. Sadly, I think an entire virtual operator
implementation would have to be added to get this to work in any way nearing
clean.
Operators are rather complex things. Many suggetsions I see for adding
operator support are designed thinking that
Class<T> where T : operator+
is sufficent.
Its not. You need
Class<T> where T : T operator+(T,T)
at the minimum. And that *still* doesn't deal with issues like int and long,
which don't actually overload any operators.
This is really a situation where you are just screwed. IArithmetic would
probably be better. It doesn't require language changes across the board,
just a change to the system valuetypes, which can pretty much be done
without adding anything new to any language and it does so in a half way
sensible way with regard to existing language rules.
Daniel,
I gave the link more for an example of an existing suggestion, not as a
suggestion I specifically endorse.
I like IArithmetic<T>, as it seems to be the "easier" route. (the KISS
principal). If only Int32 implemented it ;-) also IArithmetic<T> is in
keeping with IComparable<T>.
I agree that an actual "operator +" constraint may need to get too fancy to
be useful, especially when you consider it needs to work across languages.
But then again Microsoft has a lot of talented people working for them, and
if we (the users) keep throwing it about in the publics, hopefully something
usable can be created...
Hopefully we can see something usable included in .NET 2.0 to allow generic
arithmetic...
Jay
"Daniel O'Connell [C# MVP]" <onyxkirx@--NOSPAM--comcast.net> wrote in
message news:%2******** ********@tk2msf tngp13.phx.gbl. .. "Jay B. Harlow [MVP - Outlook]" <Ja************ @msn.com> wrote in message news:Oo******** ******@TK2MSFTN GP12.phx.gbl... Andreas, If you haven't you may want to submit a suggestion at:
http://lab.msdn.microsoft.com/vs2005/
I see at least one suggestion:
http://lab.msdn.microsoft.com/produc...5-9c64cce45d2a Unfortunatly that solution is pretty much *entirely* against the semantics of C# as it stands today. Sadly, I think an entire virtual operator implementation would have to be added to get this to work in any way
nearing clean. Operators are rather complex things. Many suggetsions I see for adding operator support are designed thinking that Class<T> where T : operator+ is sufficent.
Its not. You need Class<T> where T : T operator+(T,T)
at the minimum. And that *still* doesn't deal with issues like int and
long, which don't actually overload any operators.
This is really a situation where you are just screwed. IArithmetic would probably be better. It doesn't require language changes across the board, just a change to the system valuetypes, which can pretty much be done without adding anything new to any language and it does so in a half way sensible way with regard to existing language rules.
"Jay B. Harlow [MVP - Outlook]" <Ja************ @msn.com> wrote in message
news:%2******** ********@TK2MSF TNGP11.phx.gbl. .. Daniel, I gave the link more for an example of an existing suggestion, not as a suggestion I specifically endorse.
Sorry, I didn't mean to be harsh. I'm just rather annoyed with the operator
thing. I think I've posted that same general reply a dozen times now.
I like IArithmetic<T>, as it seems to be the "easier" route. (the KISS principal). If only Int32 implemented it ;-) also IArithmetic<T> is in keeping with IComparable<T>.
Ya, that would help termendously by helping to constrain generics to
primatives alone. I do wish there was a way to express that you want int,
uint, byte, sbyte, short, ushort, long, ulong, string, decimal, double or
float *only* in a generic. It'd help out *alot* in data situations where you
want to create classes that handle simple data. But, alas, that is a pipe
dream right now. IArithmetic wouldn't help with everything, but it'd make
numbers simpler. I agree that an actual "operator +" constraint may need to get too fancy to be useful, especially when you consider it needs to work across languages. But then again Microsoft has a lot of talented people working for them, and if we (the users) keep throwing it about in the publics, hopefully something usable can be created...
Well, I for one am not a huge fan of operator overloading in general, but it
is frustrating not to be able to perform basic mathematics with generics. It
certainly takes away the ability to *easily* do something like the stream in
STL, where the size type could be defined in the template. I've found when I use IComparable<T> for a value type (System.Int32 for example), there is no boxing going on, why would IArithmetic<T> cause boxing to go on?
Right, it shouldn't, by bad. Casting to the interfce still causes
boxing, but there shouldn't be any need for the cast if the constraint
is there.
I agree true operator constraints would probably be the best, Not entirely sure how they would work as + on Int32 is a IL opcode, while + on System.Decim al is an overloaded operator, would an operator contraint require an overloaded operator, requiring Int32 to offer the overloaded operators, although + for Int32 is already an IL opcode?
I don't know how they'd implement it. The compilers might have to
special-case the primitive types.
Either way, the JIT better be darn good at recognizing the IL sequence
and effectively produce the same native code as a simple add
instruction for primitive types, or the overhead of the method call
would hurt performance. The same is true for IArithmetic methods of
course.
My only concern with IArithmetic<T> is should there be a single interface with all math "operators" , or should there be individual interfaces, such as IAdd<T>, ISubtract<T>, IMultiply<T>, IDivide<T>, and so on that IArithmetic< T> inherits from? As I can see creating generic classes that need to support addition but (TimeSpan) but Multiplication does not make sense...
Yes that's definitely a problem too. A single interface with all the
operations is not granular enough, and wouldn't give you compile time
checking of which methods are actually supported.
Mattias
--
Mattias Sjögren [MVP] mattias @ mvps.org http://www.msjogren.net/dotnet/ | http://www.dotnetinterop.com
Please reply only to the newsgroup.
Daniel, primatives alone. I do wish there was a way to express that you want int, uint, byte, sbyte, short, ushort, long, ulong, string, decimal, double or float *only* in a generic
Why? What if I create a BetterInt32? (aka a family of types like SqlInt32 in
System.Data.Sql Types) that due to CLS requirements BetterInt32 cannot be a
generic class, so I need to do it the "old fashion way". Remember that
Generics are not CLS compatible, however you are free to use generics
internal to your project, even if the public interface is CLS compatible.
Why should generic math be limited to Int32 and not my BetterInt32?
Also as I mentioned elsewhere in this thread. int uses an IL opcode for
addition, while decimal uses an overloaded operator, so you have an
exception from the start, so enabling a constraint for just the "primitives "
is probably more work then it needs to be or should be, as it would need to
support both overloaded operators & opcodes...
One of the reasons for struct & overloaded operators is to enable us as
designers to define our own "simple data". For example Martin Fowler's
Range & Quantity patterns, both of which I would consider "simple data", and
I could see a need to enable them for generic math! http://www.martinfowler.com/ap2/ http://martinfowler.com/ieeeSoftware/whenType.pdf
Just a thought
Jay
"Daniel O'Connell [C# MVP]" <onyxkirx@--NOSPAM--comcast.net> wrote in
message news:OA******** ********@TK2MSF TNGP10.phx.gbl. .. "Jay B. Harlow [MVP - Outlook]" <Ja************ @msn.com> wrote in message news:%2******** ********@TK2MSF TNGP11.phx.gbl. .. Daniel, I gave the link more for an example of an existing suggestion, not as a suggestion I specifically endorse.
Sorry, I didn't mean to be harsh. I'm just rather annoyed with the
operator thing. I think I've posted that same general reply a dozen times now.
I like IArithmetic<T>, as it seems to be the "easier" route. (the KISS principal). If only Int32 implemented it ;-) also IArithmetic<T> is in keeping with IComparable<T>. Ya, that would help termendously by helping to constrain generics to primatives alone. I do wish there was a way to express that you want int, uint, byte, sbyte, short, ushort, long, ulong, string, decimal, double or float *only* in a generic. It'd help out *alot* in data situations where
you want to create classes that handle simple data. But, alas, that is a pipe dream right now. IArithmetic wouldn't help with everything, but it'd make numbers simpler.
I agree that an actual "operator +" constraint may need to get too fancy to be useful, especially when you consider it needs to work across
languages. But then again Microsoft has a lot of talented people working for them, and if we (the users) keep throwing it about in the publics, hopefully something usable can be created... Well, I for one am not a huge fan of operator overloading in general, but
it is frustrating not to be able to perform basic mathematics with generics.
It certainly takes away the ability to *easily* do something like the stream
in STL, where the size type could be defined in the template.
"Jay B. Harlow [MVP - Outlook]" <Ja************ @msn.com> wrote in message
news:eV******** ******@TK2MSFTN GP09.phx.gbl... Daniel, primatives alone. I do wish there was a way to express that you want int, uint, byte, sbyte, short, ushort, long, ulong, string, decimal, double or float *only* in a generic Why? What if I create a BetterInt32? (aka a family of types like SqlInt32 in System.Data.Sql Types) that due to CLS requirements BetterInt32 cannot be a generic class, so I need to do it the "old fashion way". Remember that Generics are not CLS compatible, however you are free to use generics internal to your project, even if the public interface is CLS compatible.
Why should generic math be limited to Int32 and not my BetterInt32?
I didn't *say* generic math. I said data. I write quite a few classes that
have to deal with simple data and often that data has to be interconverted
or expressed in an exceedingly simple type systems(no lists, no currency, no
custom value types, etc). I use IConvertible now and do my best to convert
bad types into valid ones, but its not the most reliable and clear method
possible.
Infact one of those links makes the point" "If you have a method that
expects to take a currency parameter, you can communicate this more clearly
by having a currency type and using it in the method declaration". I would
say it would follow that if your method cannot take BetterInt32, it isn't a
good idea to *allow* that method to take a BetterInt32, wouldn't you? Hence
a need to constrain to primative types when only primative types work. Also as I mentioned elsewhere in this thread. int uses an IL opcode for addition, while decimal uses an overloaded operator, so you have an exception from the start, so enabling a constraint for just the "primitives " is probably more work then it needs to be or should be, as it would need to support both overloaded operators & opcodes...
One of the reasons for struct & overloaded operators is to enable us as designers to define our own "simple data". For example Martin Fowler's Range & Quantity patterns, both of which I would consider "simple data", and I could see a need to enable them for generic math!
http://www.martinfowler.com/ap2/
http://martinfowler.com/ieeeSoftware/whenType.pdf
Just a thought Jay
"Daniel O'Connell [C# MVP]" <onyxkirx@--NOSPAM--comcast.net> wrote in message news:OA******** ********@TK2MSF TNGP10.phx.gbl. .. "Jay B. Harlow [MVP - Outlook]" <Ja************ @msn.com> wrote in message news:%2******** ********@TK2MSF TNGP11.phx.gbl. .. > Daniel, > I gave the link more for an example of an existing suggestion, not as a > suggestion I specifically endorse. >
Sorry, I didn't mean to be harsh. I'm just rather annoyed with the
operator thing. I think I've posted that same general reply a dozen times now.
> I like IArithmetic<T>, as it seems to be the "easier" route. (the KISS > principal). If only Int32 implemented it ;-) also IArithmetic<T> is in > keeping with IComparable<T>. Ya, that would help termendously by helping to constrain generics to primatives alone. I do wish there was a way to express that you want int, uint, byte, sbyte, short, ushort, long, ulong, string, decimal, double or float *only* in a generic. It'd help out *alot* in data situations where you want to create classes that handle simple data. But, alas, that is a pipe dream right now. IArithmetic wouldn't help with everything, but it'd make numbers simpler.
> > I agree that an actual "operator +" constraint may need to get too > fancy > to > be useful, especially when you consider it needs to work across languages. > But then again Microsoft has a lot of talented people working for them, > and > if we (the users) keep throwing it about in the publics, hopefully > something > usable can be created... > Well, I for one am not a huge fan of operator overloading in general, but it is frustrating not to be able to perform basic mathematics with generics. It certainly takes away the ability to *easily* do something like the stream in STL, where the size type could be defined in the template.
This thread has been closed and replies have been disabled. Please start a new discussion. Similar topics |
by: andrew queisser |
last post by:
I've read some material on the upcoming Generics for C#. I've
seen two types of syntax used for constraints:
- direct specification of the interface in the angle brackets
- where clauses
I looked at the files in the Gyro download but I couldn't find any
mention of constraints. Can anyone enlighten me what the current
status is and what we can expect when Generics are released?
Thanks,
|
by: Marc |
last post by:
Given a class 'Invoice' with a property 'public IMyColl<IInvoiceLine>
InvoiceLines' where 'IMyColl<T> : IList<T>' i would like to detect by
reflection that 'InvoiceLines' is a 'System.Collection.Generic.IList'.
When performing something like: 'if
(typeof(IList<>).IsAssignableFrom(propertyInfo.Type))' where 'propertyInfo'
obviously refers to the 'InvoiceLines', the result is always 'false' because
indeed 'IList<object>' and...
|
by: Michael S |
last post by:
Why do people spend so much time writing complex generic types?
for fun?
to learn?
for use?
I think of generics like I do about operator overloading.
Great to have as a language-feature, as it defines the language more
completely. Great to use.
|
by: Ole Nielsby |
last post by:
I'm puzzled at how static fields intersect with generics, as the
following snippet illustrates.
namespace MonkeyParty
{
abstract class Fruit { }
class Apple : Fruit { }
class Pear : Fruit { }
|
by: Vladimir Shiryaev |
last post by:
Hello!
Exception handling in generics seems to be a bit inconsistent to me.
Imagine, I have "MyOwnException" class derived from "ApplicationException".
I also have two classes "ThrowInConstructor" and "ThrowInFoo". First one
throws "MyOwnException" in constructor, second one in "Foo()" method. There
is a "GenericCatch" generics class able to accept "ThrowInConstructor" and
"ThrowInFoo" as type parameter "<T>". There are two methods in...
| |
by: Cedric Rogers |
last post by:
I wasn't sure if I could do this. I believe I am stretching the capability of
what generics can do for me but here goes.
I have a generic delegate defined as
public delegate bool RuleDelegate<T>(T item);
In my class my goal is to use a generic list collection to contain my
generic delegates. This will allow me to pass this List to another library
and call this list of functions. This could provide a new way to build rule
base...
|
by: SpotNet |
last post by:
Hello NewsGroup,
Reading up on Generics in the .NET Framework 2.0 using C# 2005 (SP1), I have
a question on the application of Generics. Knowingly, Generic classes are
contained in the System.Collections.Generic namespace. Literature I have
read on this ties generics in with collections, hence articulate their
examples as such. That's fine, I understand what is being said.
My question is more towards the application and implementation...
|
by: ludwig.stuyck |
last post by:
Hello,
I updated my article on generics with databinding and serialization/
deserialization.
Summary: Generics are one of the most useful improvements in the .NET
2.0 framework. In this article I start with pointing out the
disadvantages of using the classical arrays and .NET collections, and
then I show you how generics provide a nice and elegant way of
creating flexible and performant strongly typed collections with
|
by: CassioT |
last post by:
Hi. I want to create a base form class with a generic parameter.
public BaseForm<T: Form
{}
public MyForm : BaseForm<MyType>
{}
The problem here is that the inherited form doesn't work in the visual
studio designer but the execution is perfect. Without the generic
|
by: marktang |
last post by:
ONU (Optical Network Unit) is one of the key components for providing high-speed Internet services. Its primary function is to act as an endpoint device located at the user's premises. However, people are often confused as to whether an ONU can Work As a Router. In this blog post, we’ll explore What is ONU, What Is Router, ONU & Router’s main usage, and What is the difference between ONU and Router. Let’s take a closer look !
Part I. Meaning of...
|
by: Hystou |
last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can effortlessly switch the default language on Windows 10 without reinstalling. I'll walk you through it.
First, let's disable language synchronization. With a Microsoft account, language settings sync across devices. To prevent any complications,...
| |
by: Oralloy |
last post by:
Hello folks,
I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>".
The problem is that using the GNU compilers, it seems that the internal comparison operator "<=>" tries to promote arguments from unsigned to signed.
This is as boiled down as I can make it.
Here is my compilation command:
g++-12 -std=c++20 -Wnarrowing bit_field.cpp
Here is the code in...
|
by: jinu1996 |
last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven tapestry of website design and digital marketing. It's not merely about having a website; it's about crafting an immersive digital experience that captivates audiences and drives business growth.
The Art of Business Website Design
Your website is...
|
by: Hystou |
last post by:
Overview:
Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows Update option using the Control Panel or Settings app; it automatically checks for updates and installs any it finds, whether you like it or not. For most users, this new feature is actually very convenient. If you want to control the update process,...
|
by: tracyyun |
last post by:
Dear forum friends,
With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each protocol has its own unique characteristics and advantages, but as a user who is planning to build a smart home system, I am a bit confused by the choice of these technologies. I'm particularly interested in Zigbee because I've heard it does some...
|
by: agi2029 |
last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing, and deployment—without human intervention. Imagine an AI that can take a project description, break it down, write the code, debug it, and then launch it, all on its own....
Now, this would greatly impact the work of software developers. The idea...
|
by: TSSRALBI |
last post by:
Hello
I'm a network technician in training and I need your help.
I am currently learning how to create and manage the different types of VPNs and I have a question about LAN-to-LAN VPNs.
The last exercise I practiced was to create a LAN-to-LAN VPN between two Pfsense firewalls, by using IPSEC protocols.
I succeeded, with both firewalls in the same network. But I'm wondering if it's possible to do the same thing, with 2 Pfsense firewalls...
| |
by: adsilva |
last post by:
A Windows Forms form does not have the event Unload, like VB6. What one acts like?
| |