Am moving from Excel VBA to VSTO using C#. In VBA I had a 3 line custom function called IsInCollection as shown below:
On Error Resume Next
Set obj = collectionObject(itemObject)
IsInCollection = Not (obj is Nothing)
I used it all the time to check if a given workbook was open, or if a workbook contained a sheet with a particular name, etc. Because the collection and the item arguments are defined as objects it would work with anything.
I'm trying to create the same utility function/method in managed code and am struggling mightily. The problem is with the collectionObject(itemObject) expression. C# doesn't allow me to just index an object as VBA did.
If anybody can point me in the right direction it would be much appreciated. From my searching I've been looking into QueryInterface but am not sure if that's where I should be looking. It seems that an Excel object comes across as a System._ComObject, so presumably I need to iterate through that somehow(?).
TIA
i think you want to check out LINQ. for example you can query collections like
IsInCollection = obj.Any(s => s != null);
The indexing operator in C# is [], not () as in VB. I'm not very familiar with COM though, so if the object you have is just an object/System._ComObject you might have to cast it to the appropriate type first, unless _ComObject has an indexer already.
It seems that finding out what type a _ComObject really is can be be a bit difficult, so you might wanna try a trick I found at http://www.mztools.com/articles/2006/mz2006013.aspx:
var typeName = Microsoft.VisualBasic.Information.TypeName( collectionObject );
I also suspect you could use the System.ComponentModel.TypeDescriptor class.
Related
I have a huge code base and I recently made a change where I changed the type of a parameter from String to a custom class. On the next compile I got all the areas where the impact was, but areas where the input type was of type Object failed. for e.g.
String str = "32"
int i = Convert.ToInt32(str)
Now I have changed String to a new custom type lets say MyCustomClass I would now want following code to fail on next compile
MyCustomClass str = new MyCustomClass("32")
int i = Convert.ToInt32(str)
but it won't as Convert.ToInt32 also accepts type Object. Is there some way I can make a change in MyCustomClass that it's not considered Object anymore.
Please note: Convert.ToInt32 is only used for sample I have many more such functions, so please focus your suggestion/answer to question asked.
Override ToString() and IConvertible
You said in the comments that your intentions are to find places where your object, which had previously been treated as a string, and are now being treated as an object.
In these situations typically, the third-party code would call .ToString() on your object to get something which it can use.
So, Convert.ToInt32(str) is equivalent to Convert.ToInt32(str.ToString()).
If you implement ToString() and IConvertible to return whatever your old version of str looked like then it should continue to work in the same way as the old version.
Probably.
Sorry I know that is not the 100% perfect compile time answer you were looking for, but I think you also know very well that your MyCustomClass will always be considered object.
Possible compile time answer:
Write a tool which uses reflection to iterate over every class/struct/interface in every system/third-party DLL.
Output a load of CS files which contain all these same classes, but just throw NotImplementedException.
(T4 could help you do this)
Compile these classes into dummy.dll
Your .csproj now references only this one dummy.dll, instead of the real dlls.
Your project should compile fine against the dummy dll.
Look at your dummy.cs files and delete any use of object.
Re-compile... and suddenly you get a load of compile time errors showing you anywhere you are using an object.
Impliment an implicit cast from MyCustomClass to String.
public static implicit operator string(MyCustomClass str)
{
return "Legacy respresentation of str";
}
This allows the complier the choice of choosing ToInt32(Object) or ToInt32(String), and I bet it favours the later.
This way all your existing function calls will remain the same so you wont have to be concerned about third party implentation details.
(Sorry, I am not at a computer right now so I can`t test that my assumtion is correct. If you do test this, be sure to consider extension methods, as they can affect the conpilers desision making in unexpected ways)
I'm fairly new to C# but i'm looking to convert an object to an array of unsigned shorts. The original data is an array of WORD's (numerical value WORD) passed as an object. I've attempted the following but keep getting an error.
object temp = Agent.Port("PumpPressure1_01").Value;
ushort[] PP1_01 = ((IEnumerable)temp).Cast<object>()
.Select(x => x == null ? x.ToUshort())
.ToArray();
When I run this I get the following error:
'System.Collections.Generic.IEnumerable<T>' requires '1' type arguments.
The namespaces I used when I get the above error are:
using System.Linq;
using System.Text; // Don't think this is required but added it in case
If I add the following namespaces:
using System.Collections;
using System.Collections.Generic;
I get the following error.
'System.Linq.ParalleIEnumerable.Select<TTSource,TResult>()' is not supported by the language
I'm sure this is an obvious issue but I've been hunting the net for a while and can't find a solution. My best guess is that the Select function isn't correct as this was originally designed to convert an object to an array of strings.
Any help would be great.
Thanks
IEnumerable is a generic interface, so you have to declare the datatype you are using...
To be honest though, I would want to check what that call to
object temp = Agent.Port("PumpPressure1_01").Value;
is actually returning - by inspecting it in the debugger... If it is simply returning a reference to an array of a numeric type, you should be able to simply cast it. What you are doing though is trying to cast each individual item within the array - I suspect that's not what you should be doing - which would be casting the array itself.
Can you give us any API documentation for the Port method on the Agent object so I can see what it is meant to return? Can you try the inspection and see what that gives you?
Why you casting to IEnumerable and then casting it back to object if your temp variable is already of type object?
Also IEnumerable<T> is a generic interface and must specify exact type (as exception also says to you). If you have an array of integers and you want to work with them it should be IEnumerable<int>
Thanks for all the help and feedback.
Unfortunately I was't paying enough attention to the warnings that was posted which seems to be causing the issue.
Warning: Reference to type 'System.Func '2' claims it is defined in 'c:\Windows\Microsoft.NET\Framework64\v2.0.50727mscorlib.dll'. but it could not be found
It seems that there is some issue with the .NET reference. I have another VM which I tested the following solution on and it seemed to work without issue. Looks like I'll have to reinstall the software package to get it to work on the VM i want to use.
The software package I'm using is a custom package that uses C# to build solutions with prebuilt classes made to look like plug and play blocks. You can connect the blocks together drawings lines from one input/output of a block to another. You can then build C# code inside the blocks. Basically c# for dummy's like me..
Example of the blocks:
As for the code, I did have to make some changes as follows but now works a treat. Agent.Port("PumpPressure1_01").Value.RawValue is used to reference the particular ports on the block.
object temp = (object)Agent.Port("PumpPressure1_01").Value.RawValue;
UInt16[] PP1_01 = ((System.Collections.IEnumerable)temp).Cast<object>()
.Select(x => Convert.ToUInt16(x))
.ToArray();
foreach(UInt16 x in PP1_01)
{
Agent.LogDebug("values: " + x.ToString());
}
Again, thanks for all the help. Just need to resolve the issue with the library reference now.
I am looking at Amibroker's OLE documentation examples in VBScript and JS trying to convert it to C# code:
http://www.amibroker.de/guide/objects.html
In it it says:
Filter( 0, "index" ) = 1; // include only indices
Filter( 1, "market" ) = 2; // exclude 2nd market
I have a C# dynamic object that I built, and I can find and call the Filter() function, but I have no idea how to set the value after the function call, since that is not valid C# syntax.
Here is the C# code:
var type = Type.GetTypeFromProgID("Broker.Application");
dynamic ab = Activator.CreateInstance(type);
ab.Analysis.Filter(0, "market") = 2; // This is obviously not compiling
When I call ab.Analysis.Filter(0, "market"), it simply returns an int for the current setting. Is the answer to use reflection somehow? I haven't tried to go down that route wondering if there is a simpler solution.
That code snippet you found is jscript, not VBScript. It is not a function property, it is an indexed property. VB.NET supports them well. But the C# team did not like them and only permits one indexed property for a class, the indexer (this[]). By popular demand they added support in version 4. Only for COM interop. Which is what you are using.
Just like the indexer, you use square brackets for indexed properties:
AA.Filter[0, "market"] = 1;
Which should be supported by dynamic as well. Explicitly calling the setter function would be another way, AA.set_Filter(0, "market", 1).
Note that you'll have a much easier time writing this code when you add a reference to the type library. That lights up IntelliSense and the red squiggles.
I have the following recursive function that is used to search down a hierarchical tree and remove found objects from a list:
private List<Tag> RemoveInvalidTags(Device device, List<Tag> tags)
{
var childDevices = device.ChildDevices.Select(c => c.ChildDevice);
foreach (var child in childDevices)
{
tags.Remove(child.Tag);
RemoveInvalidTags(child, tags);
}
return tags;
}
What I am expecting this to do is remove all child device tags at this level from the tags list, call the function recursively for your children, then return that list up to the previous level.
Will this pass the tags list by reference and modify the original passed list? Or should I be doing something along the lines of
validTags = CollectValidTags(child, tags);
and adding up all the returned lists?
Will this pass the tags list by reference
No. The list object is passed "by value" (but see next). (ref or out is required to "pass by reference" in C#, but that is not being done here, nor does it need to be.)
and modify the original passed list?
Yes. This is because the list object is passed. And that list object is mutated. Passing a reference type (anything defined with class) never implicitly makes a copy/clone/duplicate. An object is what it is.
Now, back to "pass by value": the "value passed" is the value of the "reference" (internal, no need to concern with this): this calling strategy is better known as Call/Pass By Object Sharing in a langauge like C#. The same object is shared (just as if it were assigned to two different variables). (Value types -- a struct -- are different in that they (often) are copied/duplicated on the stack, but a List<T> is a class.)
Or should I be doing something along the lines of
It depends upon the desired semantics. Is the caller expecting the side-effects directly or indirectly? Can the mutation side-effect lead to unexpected scenarios? Make sure to document it either way. (I prefer the way that guarantees the initial object is not mutated.)
Hope that clears some things up.
Happy coding.
In your code you are modifying the items in your tags parameter and passing back the modified list as your result. You want to avoid modifying lists in this way - especially inside loops where it can cause you grief in many situations.
I have a LINQ-based alternative for you.
If I understand the intent of your code you want to do something like this:
Func<Device, IEnumerable<Device>> flatten = null;
flatten = d =>
{
return (new [] { d }).Concat(
from c in d.ChildDevices
from f in flatten(c)
select f);
};
var collectedValidTags = flatten(device).Select(d => d.Tag);
var result = tags.Except(collectedValidTags).ToList();
This approach doesn't pass your list of tags around so there is no chance of modifying your original list.
Does this help?
Short answer - your code will do what you want.
Long answer - you should read descriptions of what the ref keyword does. I would suggest you read as many descriptions as possible; there are many different ways to articulate it ("I like to think of it as... ") and some will work for you whilst others won't. If you read many descriptions (from people who understand it) then some kind of understanding should gel for you.
Here's a list to get you started:
Use of 'ref' keyword in C# (my answer)
C# ref keyword usage
Passing by ref?
Example of practical of "ref" use
I have a combo box that is populated with an Arraylist, like below. If I have another instance of same object, how do I select that object in the combobox? Please look at the code below to understand.
MakeEntity selectedMake = Make.GetMakeByTitle("Honda");
List<MakeEntity> allMakes = Make.GetAllMakes();
cbVehicleMake.DataSource = allMakes;
cbVehicleMake.SelectedIndex = cbVehicleMake.Items.IndexOf(selectedMake);
But last line is not working as expected. Can I get it to run at all or am I going in the wrong direction? Should MakeEntity implement iComparable?
Assuming MakeEntity has an property called id!
cbVehicleMake.SeletedItem=allMakes.Find(q=>q.Id==selectedMake.Id))
You shouldn't need to implement IComparable for IndexOf, just Equals. Otherwise it will default to Object.Equals, which only matches if the two references are to the same instance. (Not sure if this is a problem or not without seeing the definition of MakeEntity.)
Also, just use:
cbVehicleMake.SelectedItem = selectedMake;
This will internally handle finding the object in the options.
Documentation: http://msdn.microsoft.com/en-us/library/system.windows.forms.combobox.selecteditem.aspx