Assignment Expression in VB.Net - c#

Unlike a = b = 5 in VB.NET - impossible?,
I am specifically asking for an extension method to overcome this.
In C#, you can do something like this, which is very helpful:
a = b = c = 16;
All variables end up being 16.
One of the answers in Why do assignment statements return a value? gives reasons why this is handy.
But in VB.Net, if you do:
Dim a, b, c As Integer
a = b = c = 16
All you get are 0's for a, b, and c.
I want to defeat this limitation by an extension method. Can this be done?
EDIT:
Here is the closest answer I personally could come up with:
<System.Runtime.CompilerServices.Extension()>
Public Function Assign(ByRef Operand1 As Object, ByRef Operand2 As Object) As Object
Operand1 = Operand2
Return Operand1
End Function
Even though it allows you do do this,
Dim a, b, c As Integer
a.Assign(b.Assign(c.Assign(16)))
it sure is clunky and imo harder to follow, but it's the closest thing to an direct answer to my actual question I could find. I welcome any improvements.

As mentioned in a = b = 5 in VB.NET = impossible? by γηράσκωδ'αείπολλάδιδασκόμε, VB uses the same operator for assignment and equality, and you can't make extension operators per Tim's comment, so the only way to do this is through an extension method like you have found.
The syntax of your extension method can be cleaner if you overload it to accept multiple arguments; I'll guess that 3 assignments is enough. Here it's written for the Integer type so you don't have to bother casting, but use Object if you prefer.
<System.Runtime.CompilerServices.Extension> _
Public Sub Assign(ByRef aInt As Integer, ByRef newInt As Integer)
newInt = aInt
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Sub Assign(ByRef aInt As Integer, ByRef newInt1 As Integer, ByRef newInt2 As Integer)
newInt1 = aInt
newInt2 = aInt
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Sub Assign(ByRef aInt As Integer, ByRef newInt1 As Integer, ByRef newInt2 As Integer, ByRef newInt3 As Integer)
newInt1 = aInt
newInt2 = aInt
newInt3 = aInt
End Sub
Usage:
Dim a, b, c, d As Integer
a = 16
a.Assign(b)
a.Assign(c, d)
Console.Write("b = {0}; c = {1}; d = {2}", New Object() {b, c, d})
a = 99
a.Assign(b, c, d)
Console.Write("b = {0}; c = {1}; d = {2}", New Object() {b, c, d})
You could keep going and add more overloads if you like, including one with an array or list if you want N assignments.

Related

Imported C-function works in C# but not in VB6

I'm working on a problem for a few days now and can't seem to find the answer. I have a third party dll written in C, which i have to use in an VB6 application.
The function inside dll looks something like this:
someFunction(WContext* context, const unsigned char* seed, int seedLength, const unsigned char* name, int nameLength,
const unsigned char* pin, int pinLength, const char* description)
I have an example written in c#. I tried it out and it works just fine. This is what it looks like in C#
[DllImport("Filename.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern int someFunction(IntPtr context, byte[] seed, int seedLength, byte[] name, int nameLength,
byte[] pin, int pinLength, string description)
This is later used in the example like this:
byte[] seed = Encoding.ASCII.GetBytes("seed")
byte[] name = Encoding.ASCII.GetBytes("name")
byte[] pin = Encoding.ASCII.GetBytes("1234")
string description = "description"
int result = someFunction(context, seed, seed.length, name, name.Length, pin, pin.Length, description)
Now this works just fine in C#. I get 0 as a result which means in that case that the operation was a success.
I want to make this work in VB6. I tried it with some other functions of the dll and they work just like they should. This one gives me a headache. Here is what I tried just like with any other function:
First I imported the function into my code so I could use it later. I did this with a few other functions the same way and it worked fine.
Private Declare Function someFunction Lib "C:\DllPath\Filename.dll" (ByVal context As Long, ByRef seed as Byte, _
ByVal seedLength As Integer, ByRef name As Byte, _
ByVal nameLength As Integer, ByRef pin As Byte, _
ByVal pinLength As Integer, ByVal description As String) As Integer
Next Step was for me to call the function. I did it this way:
(I do get the context from another function earlier so this already has a value. Other functions work fine with that variable)
Dim seed() As Byte
Dim seedLength As Integer
Dim name() As Byte
Dim nameLength As Integer
Dim pin() As Byte
Dim pin As Integer
Dim description As String
Dim result as Integer
seed = StrConv("seed", vbFromUnicode)
seedLength = UBound(seed) + 1
name = StrConv("name", vbFromUnicode)
nameLength = UBound(name) + 1
pin = StrConv("1234", vbFromUnicode)
pinLength = UBound(pin) + 1
description = "description"
result = someFunction(context, seed(0), seedLength, name(0), nameLength, pin(0), pinLength, description)
The value for result is 1. By the documentation I got this means invalid parameter. Now I researched a lot. Read that in VB6 I have to give the first element of the byte array just like I did in my code. Tried it first with the whole array, got the same result. I think it has something to do with the arrays since its the first function that I've taken over to my code that had those. I'm also not really that native with VB6 but I have to do this. Maybe some of you guys know why this doesn't work. Probably just a minor mistake by myself.
In VB6 it's as simple as this
Option Explicit
Private Declare Function someFunction Lib "C:\DllPath\Filename.dll" (ByVal context As Long, ByVal seed As String, _
ByVal seedLength As Long, ByVal name As String, _
ByVal nameLength As Long, ByVal pin As String, _
ByVal pinLength As Long, ByVal description As String) As Long
Private Sub Form_Load()
Dim context As Long
Dim seed As String
Dim name As String
Dim pin As String
Dim description As String
Dim result As Long
seed = "seed"
name = "name"
pin = "1234"
description = "description"
result = someFunction(context, seed, Len(seed), name, Len(name), pin, Len(pin), description)
End Sub
Just don't use Integer at all (only Longs) use ByVal ... As String for the runtime to do the Unicode->ANSI conversion and be done with it.
The original API is weirdly declared with unsigned char * pointer and separate int length parameter probably because the seed, name and pin strings can contain \0 embedded while description is plain zero-terminated string which explains the PITA w/ the C/C++ prototype.
VB6 strings are length-prefixed (and zero-terminated) from get go and can contain Chr$(0) by design so there is no need to take any additional measures like in C# sample w/ explicit byte[] arrays conversion and function prototype signature fiddling. Just use ByVal ... As String in the API declare.
When calling a declared API, VB6 will convert a String to an ANSI byte array automatically, so you can declare seed, name, and pin as ByVal Strings.
Description is a bit confusion to me though, since I don't see a length being passed for that one. Is this really a null-terminated string (pszDescription)? How does the API you are calling know how long this character buffer is?
You can try to change the function declaration in VB, making seed, name, and pin ByRef As Byte(),
note the parentheses:
Private Declare Function someFunction Lib "C:\DllPath\Filename.dll" (ByVal context As Long, ByRef seed as Byte(), _
ByVal seedLength As Integer, ByRef name As Byte(), _
ByVal nameLength As Integer, ByRef pin As Byte(), _
ByVal pinLength As Integer, ByVal description As String) As Integer
Then, when calling the function, pass the entire arrays, not the first elements:
result = someFunction(context, seed, seedLength, name, nameLength, pin, pinLength, description)

Method with a parameter that accepts Different types of Enum values

I have a method that accepts Enum values as parameter, this answer is my reference. However I cant seem to make it work.
Enums
Public Enum FieldTypes
db_Alpha = 0
db_Memo = 1
db_Numeric = 2
End Enum
Public Enum SubTypes
st_None = 0
st_Phone = 35
st_Percentage = 37
st_Address = 63
st_Link = 66
End Enum
Method
Public Sub Foo(Of T)(ByVal Param1 As String, ByVal EnumParam As T)
Dim param As Type = GetType(T)
If param.IsEnum Then
Dim x = param.GetEnumValues()
End If
'Running some codes here
End Sub
Main
Public Sub Test()
Foo("TestName",FieldTypes.db_Memo)
End Sub
Im getting all the values of the type of Enum that was passed as a parameter.
I would want to reduce Method overloading and learn about generics here. Accepting C# and VB.net suggestions.
How could I get the value of of the Enum item passed?
How can I restrict this, that only the two types of Enum written is accepted?
Since an Enum is always a value type you can restrict your function "Foo" to only accept value types.
Public Sub Foo(Of T As Structure)(ByVal Param1 As String, ByVal EnumParam As T)
You could also restrict your generic function to only accept reference types
Public Sub Test(Of T As Class)(ByVal param As T)
or only accept types that implement a specific interface
Public Sub Test(Of T As IDispose)(ByVal param As T)
You can even concatenate those conditions
Public Sub Test(Of T As Class, IDispose, IEnumerable)(ByVal param As T)
Inside your function "Foo" you can check, that T is of the correct type like this:
If Not GetType(T).Equals(GetType(FieldTypes)) AndAlso _
Not GetType(T).Equals(GetType(SubTypes)) Then
Throw New Exception("Enum type not supported")
End If
To get the numeric value of "EnumParam" you can convert it to Integer or whatever else numeric type your enumeration is based on:
Dim x As Integer = Convert.ToInt32(EnumParam)

Linq Select compiling differently between C# and VB

I have the following C# code which compiles correctly:
private string formatterCSharp(int number)
{
return "n" + number;
}
private void testInCSharp()
{
IEnumerable<int> list = new List<int>();
IEnumerable<string> formatted = list.Select(formatterCSharp);
}
As you can see, formatted should contain the contents of list, with formatterCSharp applied to each.
When I try to replicate this code in VB, I come up with this:
Private Function formatterVisualBasic(ByVal number As Integer) As String
Return "n" + number
End Function
Private Sub testInVB()
Dim list As IEnumerable(Of Integer) = New List(Of Integer)
Dim formatted As IEnumerable(Of String) = list.Select(formatterVisualBasic)
End Sub
However I get two compilation errors on the Select statement in VB.
BC30455
Argument not specified for parameter 'number' of 'Private Function formatterVisualBasic(number As Integer) As String'.
BC30518
Overload resolution failed because no accessible '[Select]' can be called with these arguments:
Extension method 'Public Function [Select](Of TResult)(selector As Func(Of Integer, TResult)) As IEnumerable(Of TResult)' defined in 'Enumerable': Type parameter 'TResult' cannot be inferred.
Extension method 'Public Function [Select](Of TResult)(selector As Func(Of Integer, Integer, TResult)) As IEnumerable(Of TResult)' defined in 'Enumerable': Type parameter 'TResult' cannot be inferred.
Try
Dim formatted As IEnumerable(Of String) = list.Select(AddressOf formatterVisualBasic)
In VB.net you can't specify function name to pass it like that.
https://msdn.microsoft.com/en-us/library/y72ewk2b.aspx
Okay first off the select query is formed differently in VB.NET, here's how it should look:
Dim formatted As IEnumerable(Of String) = list.Select(Function(x As String) x = formatterVisualBasic(mynumber))
Secondly, if you want to get the same exception as in your C# code, you must add parentheses to the function call formatterVisualBasic, so that it is recognized as a function
Dim formatted As IEnumerable(Of String) = list.Select(Function(x As String) x = formatterVisualBasic())

Condition if differences in C# and VB

Why does conditional if in VB require not handle the direct cast of the conditions. For example in C# this is just fine...
bool i = false;
i = (1<2)? true:false;
int x = i? 5:6;
But if I wanted the same thing in VB I would have to cast it
Dim i as Boolean = CBool(IIF(1<2, True, False))
Dim x as Integer = CInt(IIF(i, 5, 6))
I don't understand why C# will do the transform and why VB does not. Should I be casting on my C# conditionals eg
bool i = Convert.ToBoolean((1<2)? True: False);
int x = Convert.ToInt32(i? 5:6);
Also, Yes I am aware that IIF returns type object but I would assume that C# does as well as you can return more than just True|False; it seems to me that C# handles the implicit conversion.
IIf is a function and is not equivalent to C#’s ?:, which is an operator.
The operator version has existed for a while in VB.NET, though, and is just called If:
Dim i As Boolean = If(1 < 2, True, False)
… which is, of course, pointless, and should just be written as:
Dim i As Boolean = 1 < 2
… or, with Option Infer:
Dim i = 1 < 2
This code will show you the difference between the IIf function and the If operator. Because IIf is a function, it has to evaluate all of the parameters to pass into the function.
Sub Main
dim i as integer
i = If(True, GetValue(), ThrowException()) 'Sets i = 1. The false part is not evaluated because the condition is True
i = IIf(True, GetValue(), ThrowException()) 'Throws an exception. The true and false parts are both evaluated before the condition is checked
End Sub
Function GetValue As Integer
Return 1
End Function
Function ThrowException As Integer
Throw New Exception
Return 0
End Function

Adding a default convert to a .net object

We had the need to create the following object, basically a way to pass in integer or strings and fetch these back out as either an integer or a zero-padded string:
Public Class DOC_NUMBER
Private m_DOC_NUMBER As Integer = 0
Public Sub New(ByVal DOC_NUMBER As Integer)
m_DOC_NUMBER = DOC_NUMBER
End Sub
Public Sub New(ByVal DEPOT_CODE As String)
Dim ParseInput As Integer = 0
If Integer.TryParse(DEPOT_CODE, ParseInput) = False Then
m_DOC_NUMBER = 0
Else
m_DOC_NUMBER = ParseInput
End If
End Sub
Public Overrides Function ToString() As String
Return Right("0000000000" & m_DOC_NUMBER.ToString(), 10)
End Function
Public Function ToInteger() As Integer
Return m_DOC_NUMBER
End Function
End Class
An instance of this class can then be set as follows:
Dim DocumentID As New DOC_NUMBER("00123")
Dim DocumentID As New DOC_NUMBER(123)
This can then be assigned to other vars like this:
Dim NewDocumentID as Integer = DocumentID.ToInteger()
Dim NewDocumentID as String = DocumentID.ToString()
99% of the time however we'll be dealing with integers, so we'd like to make the .ToInteger() optional, for example:
Dim NewDocumentID as Integer = DocumentID
Obviously this currently gives the error "Value of type 'X.DOC_NUMBER' cannot be converted to 'Integer'"
How do we modify the class to automatically pass out an integer it's being assigned to an integer?
Examples in any .net language will be fine.
You can do what you want with implicit conversions. In C# that might look like this:
public static implicit operator string(DOC_NUMBER docNum)
{
// implement
}
public static implicit operator int(DOC_NUMBER docNum)
{
// implement
}
The VB.NET equivalent is the Widening Operator.
However, I'll point out that I find ToInteger() and ToString() (or as properties: AsInteger and AsString, perhaps) much clearer and simpler, and therefore better, than implicit conversions.

Categories

Resources